Using WebAIM with ASP.NET 2.0 and ASP.NET AJAX, Part 5: Receiving Instant Messages
by Christian Wenz
July 24, 2007
AOL’s WebAIM API provides a very comfortable means to incorporate a JavaScript-powered AIM client in a web page. With some extra effort, it is also possible to control most of WebAIM's features using a server-side technology. The preceding four articles in this series showcased some of the features of ASP.NET AJAX and the ASP.NET AJAX Control Toolkit and implemented the first building blocks to write a web interface to AIM, including sending instant messages (IMs) to AIM contacts. This article is the final installment of the series and continues where the previous part ended: after sending IMs, the application will finally receive the functionality to receive messages, as well. As always, make sure that you have read the previous articles, including the information regarding registering as a WebAIM developer and setting up the development and production environment.
The Event Polling Mechanism
When the ASP.NET-AJAX-powered WebAIM application retrieves the buddy list, it calls the server side, which then calls the fetchEvents API method. The specific URL of this REST web service call has been retrieved by the previous startSession call; the fetchBaseURL property contains a specific URL to fetch any pending events from the server.
If you have been asking yourself why this URL could ever change, here is the answer: a call to fetchEvents retrieves all events from the server for the current session, both events you have already retrieved and events that are new. This would cause the application to manage events. However, within the data returned by every fetchEvents call there is a new fetchBaseURL value, which basically adds a sequence number to the URL. Using the new fetchBaseURL only returns new events, and skips the old ones.
Previously, we only called fetchEvents once, to retrieve the buddy list. The server-side function was appropriately called getBuddies(). However, this did not take into account that buddies may sign on or sign off, which changes the current buddy list. Therefore, fetchEvents needs to be called repeatedly, always using the latest fetchBaseURL value.
What does this have to do with receiving instant messages via the web application? Incoming messages are also events that are returned using fetchEvents. Remember the URL that was used when starting the WebAIM session:
http://api.oscar.aol.com/aim/startSession?f=json&k=***&a=***&events=im,buddylist
This call subscribes to two events: buddy list changes and incoming instant messages. Therefore, repeated calls to fetchEvents do not only keep the buddy list updated, but also retrieve incoming IMs.
Refactoring, Again
In order to add this functionality to the existing web application, some changes to the existing code base are necessary. First of all, the getBuddies() method (in the WebService.asmx file) will be renamed to fetchEvents(). Of course there is no technical reason for doing so, but with the new features, this name does make much more sense. As a logical conclusion, all JavaScript calls to getBuddies() in the WebAIM.js file need to be changed to fetchEvents() calls, as well.
The behavior of the fetchEvents() server method needs to be altered, as well. Since the previous version of the web application only called fetchEvents once, the fetchBaseURL returned from the call was discarded. Since we are now planning to call fetchEvents again and again, the fetchBaseURL value needs to be updated properly. Here is the changed code:
[WebMethod(EnableSession = true)]
public string getEvents()
{
if (Session["fetchBaseURL"] == null)
{
throw new Exception("Failed to read fetchBaseURL");
}
string myURL = Session["fetchBaseURL"].ToString() + "&f=json";
HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(myURL);
myRequest.Referer = System.Configuration.ConfigurationManager.AppSettings["BaseURL"];
HttpWebResponse myResponse = (HttpWebResponse)myRequest.GetResponse();
if (myResponse.StatusCode != HttpStatusCode.OK)
{
throw new Exception("Status: " + myResponse.StatusCode.ToString());
}
Stream s = myResponse.GetResponseStream();
StreamReader rs = new StreamReader(s, Encoding.UTF8);
string json = rs.ReadToEnd();
Regex r = new Regex("\"fetchBaseURL\"\\s*:\\s*\"(.*?)\"");
Match m = r.Match(json);
if (m.Value != String.Empty && m.Groups.Count == 2)
{
Session["fetchBaseURL"] = m.Groups[1].Value;
}
return json;
}
Now to the client side: the JavaScript functions are also properly renamed, to getEvents() and getEventsCallback(). Let’s start with the former: apart from renaming it, we also implement a mechanism to avoid calling the server method if another call is still pending. Remember that we are providing quite an effort to call the WebAIM API: JavaScript code uses ASP.NET AJAX to call an ASP.NET web service that in turn calls the WebAIM API. Therefore, such a call might take some time.
We define two global JavaScript variables. One holds the amount of seconds between fetchEvents() calls (in milliseconds--we use 10,000, or ten seconds). The other remembers whether there is a pending fetchEvents() call. If there is such a call, fetchEvents() is not called again.
var _interval = 10000;
var _eventPending = false;
function getEvents() {
if (!_eventPending) {
dump("Loading events");
_eventPending = true;
WebService.getEvents(getEventsCallback, myError);
}
}
If you recall the new checkURL() function from the previous article in the series, you might have wondered what this code snippet is good for:
if (secondConsent) {
_intervalID = window.setInterval("getEvents();", _interval);
dump("second consent");
sendMessage(_context.buddy, _context.message);
_context = null;
}
Now you know: once the user has granted the application to send IMs on his/her behalf, we can repeatedly call getEvents(). This getEvents() interval could actually be moved directly to the startSessionCallback() function (instead of the one-time getEvents() call there), but when developing and debugging the application, it is always a better idea to do just a few API calls, as late as possible. One reason for this is that WebAIM keys have some limitations, as outlined in the license agreement (on developer.aim.com/license, scroll down to the addendum and look for "Usage Limitations"). The amount mentioned there is quite generous (currently 250,000 API calls per day and two million calls per month); however, there are other limitations that are enforced when you do repeated API calls. Should you ever get the depicted error message in the debug console, you need to wait some time until you are allowed to do any WebAIM calls again (in practice, about 30 minutes of patience will do).

Figure 1. The server thinks that you called the WebAIM API too often
Retrieving Messages
Whenever new messages are sent from the server, the getEventsCallback() JavaScript function is where the client retrieves them. Back when the function was still called getBuddiesCallback(), only the event type "buddylist" was checked for; now other events must be taken care of, as well. Therefore, a switch statement comes in handy:
switch (ev.type) {
case "buddylist":
...
The event name for incoming IMs is "im". A typical JSON string retrieved by fetchEvents can look similar to this (shortened version):
{
"response":{
"statusCode":200,
"statusText":"Ok",
"requestId":"12345",
"data":{
"timeToNextFetch":500,
"fetchBaseURL":"http://...",
"events":[
{
"type":"im",
"eventData":{
"message":"hi!",
"source":{
"displayId":"myBuddy"
}
}
}
]
}
}
}
Of course, there is more information in the returned JSON than shown, but the most important bits can be seen:
- The event
typeis"im". - Within
eventData, themessageproperty contains the sent message. - Within
eventData, thesourceproperty contains information about the sender, including the display name (displayId).
Before outputting all this information in our application, one word of caution: instant messages can be sent as text or HTML. Especially modern clients are sending HTML, since users can set fonts and colors. A typical HTML instant message may look like this (sample produced with the Mac OS X AIM client):
<div style="background-color: #FFFFFF"><span style="font-size: 1.00em; font-family: 'Lucida Grande'">hi!</span></div>
Since HTML markup could theoretically either destroy our layout or, even worse, contain JavaScript code, we are filtering out all markup information, by using this regular expression:
message.replace(/<.*?>/g, "")
This strips out all HTML tags. For sanitizing other information, this helper function comes in handy:
function htmlEncode(s) {
return s.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
But now back to getEventsCallback()! When the "im" event type is retrieved by the fetchEvents API, the message is put at the top of the draggable message window:
case "im":
dump("Received IM!");
var buddy = ev.eventData.source.displayId;
var message = ev.eventData.message;
$get(_messagesID).innerHTML =
"<b>From " + htmlEncode(buddy) + ": </b>" +
htmlEncode(message.replace(/<.*?>/g, "")) + "<br />" +
$get(_messagesID).innerHTML;
break;
The global variable _messagesID contains the ID of the <div> element holding the incoming (and sent) messages, making the code more flexible that way.

Figure 2. A "real" client sends some messages

Figure 3. And our web client happily receives them
Logging Out
As a final feature, a logout button will be added to the page. Otherwise, it may happen that logging in from one machine and not logging out from another will prompt AIM to send you a warning message.
The UI for that is easily assembled: a simple HTML button will do, but you could of course also go with something more fancy:
<input type="button" id="btnLogout" value="Logout" onclick="logout();" />
Logging out requires the application to send a request to the following WebAIM API URL:
http://api.oscar.aol.com/aim/endSession?f=json&aimsid=***
Of course, this could also be done by the web server, but in this case, the client can fulfill this task, as well.
ASP.NET AJAX comes with a feature to send an HTTP request; actually, that’s "just" an interface to a well-known JavaScript object, XMLHttpRequest. So in theory, something like this would work:
var r = new Sys.Net.WebRequest();
r.set_url(
"http://api.oscar.aol.com/aim/endSession?f=json&aimsid=" + escape(_aimsid));
r.set_httpVerb("GET");
r.add_completed(function() {dump("logged out");});
r.invoke();
However, there is a catch: for (obvious) security reasons, such a Sys.Net.WebRequest() call (or generally, any XMLHttpRequest call) may only go to the origin of the current page, the origin being defined by the protocol, port, and domain. Here, this is obviously not the case, since our web application does not reside on api.oscar.aol.com. However, there are other ways to send GET requests to remote servers using JavaScript; for instance, by dynamically loading an image:
function logout() {
if (_aimsid != null) {
(new Image()).src = "http://api.oscar.aol.com/aim/endSession?f=json&aimsid="
+ escape(_aimsid);
}
}
It does not matter that http://api.oscar.aol.com/aim/endSession?f=json&aimsid=*** does not return an image--the browser does send the HTTP request, and that’s all we need.
After sending this request, fetchEvents will once again return something, this time the confirmation that the session has actually been ended and the user is now logged out. In the big switch statement in getEventsCallback(), this is taken care of:
case "sessionEnded": window.clearInterval(_intervalID); $get(_outputID).innerHTML = "<p>Session ended</p>"; break;
And that concludes our sample application!

Figure 4. The user has successfully logged out
Next Steps
This series showed a quite unusual combination: using ASP.NET AJAX to access the WebAIM API. The idea of the API is that JavaScript code can call it, but we demonstrated how it is possible to consume this API from the server side, as well.
The code so far is, of course, only the beginning. The WebAIM API offers many other features that could be integrated in the application. There are some issues, too. When the server is stalling, it takes the code quite some time to notice; a JavaScript timeout mechanism could be implemented to amend the situation. Also, individual windows for every contact would be nice--another JavaScript effect to be added to the application.
Implementing this application proved to be quite hard at times. Debugging JavaScript is complicated enough, but debugging both remote JavaScript calls and HTTP requests the server is sending was no easy task. The software industry is working hard to create better tools to facilitate developers' lives, so closely watching this area is worthwhile. See you on AIM!
Resources
The source code for this article series is located in the zipped file WebAIM-code.zip.
References
- "Using WebAIM with ASP.NET 2.0 and ASP.NET AJAX, Part 1" by Christian Wenz: The first article in this series
- The Microsoft Ajax home page
- The free Microsoft Visual Web Developer Express Edition page
- The ASP.NET AJAX Control Toolkit
- The Firebug Firefox browser extension
- The Web Development Helper for Internet Explorer
