Thursday, February 9, 2012

Improving the Sametime Connect Web API (a bit) with a Dash of jQuery

Previously, we discussed using some of the features built in to the Sametime SDK/Connect Web API to check the presence of a user, or in our case a queue, before initiating a conversation through an internal portal page. At a high level, our 'click to chat' web application needs to detect whether a Sametime Connect client is available, or whether we should initiate a conversation using our web client.  We checked the user's presence before starting a conversation because we found, in some cases, that the Sametime web API wouldn't be able to tell if a user was online or not, and an attempt to launch a Sametime conversation to that user (or queue) would invariably fail, so we needed to launch our web client in these cases.

The previous blog post resolved a lot of our problems with presence detection, but it also uncovered some additional issues related to successfully detecting whether to launch our web client or the native Sametime client. More specifically, our custom Instant.detectPresenceOfUser(' [user] ') JavaScript function would work fine (and would return true indicating that Sametime could see the user online) and it should therefore be able to launch a Sametime conversation with that user. However, this is where the trouble begins; even though our code would detect a native Sametime client, the Sametime window would never appear and our web client would launch by default. In most test scenarios we would get the web client about 4 out of 5 times (even when the person had a local Sametime client installed). Clearly there was an issue in the API code for invoking the Sametime window.

Below you will see the code as it exists in the Sametime Connect Web API, the function is called sametime_invoke, it takes in a series of parameters which we will lay out below.

 var sametime_invoke = function(action, userId, params)  
 {  
      var d = new Date();  
      var url = sametime_servletUrl + action+'?userId='+userId;  
      if(params != null)  
           url += "&" + fixupParams(params);  
      url += "&time=" + d.getTime();  
      var imgObj =new Image();  
      imgObj.src = url;  
 }  

First, the function requires an action, in our case, chat. Next a userId, following the same example as our previous post, System p. And last it takes in some optional parameters which we aren't using. So normally, our JS to invoke the Sametime client would look like this:

 sametime_invoke('chat', 'System p');  

This would launch a chat conversation with the 'System p' user. Through our testing though, this sametime_invoke function proved to be deficient, working properly only a handful of times. Looking at the sametime_invoke function, it appeared as though the last two lines of the function are where the magic request happens to spring the window. Stepping through the function logically, we can follow it like this:

  var sametime_invoke = function(action, userId, params)   
  {   
    // Creates a new JavaScript Date object, this is used later to create a timestamp   
    var d = new Date();  
      
    // Forms the URL which it will post to, using the parameters we passed in (action, userId, params)   
    var url = sametime_servletUrl + action+'?userId='+userId;  
      
    // Looks to see if we have passed in parameters  
    if(params != null)  
       url += "&" + fixupParams(params); // Appends them to the URL  
      
    // Adds a timestamp to the end of the URL using the Date object (d) we created above.   
    url += "&time=" + d.getTime();  
      
    // Creates an image object on the page  
    var imgObj =new Image();   
      
    // 'Loads' the image using the URL we built. This makes a request to the URL, and should open the window.   
    imgObj.src = url;  
  }  

The issue with inconsistent launching appears to come from the last two lines. The creating of the Image object, and staging the image request to the URL provided doesn't appear to be the best approach to this. It also lacks the ability to detect any failures, its essentially a blind post. That being said, all of the code leading up to those two lines, is very useful, and tells us quite a bit about what the Sametime client's servlet requires in order for us to make a POST (or in our case, for this article, a GET) to launch that window.

By visiting the URL that it builds (http://localhost:59449/stwebapi/chat?userId=Demo%20Sales&time=1328650958738, for our purposes), we get a launched Sametime client chat to our 'Demo Sales' queue, and we also get, in the browser, a response from the servlet, formed as parsed JSON (JSONP). This is very useful. This response (returnCode:200), lets us know that the servlet understood our request, was able to find the user, and launch the Sametime window. Unfortunately, this doesn't help us detect presence, so if the user is offline, it'll still launch the window, it will just error once it does saying the user is offline. So we will still need to do those presence checks up front using our Instant.detectPresenceOfUser function.

So, enough talk about it, lets re-write that sametime_invoke function using jQuery, AJAX, and a callback function.

 Instant.launchSametimeConversation = function (stid) {  
   var time = new Date();  
   var getUrl = 'http://localhost:59449/stwebapi/chat?userId=' + stid + '&time=' + time.getTime() + '&jsonp=Instant.handleResponse';  
   
   // Use jQuery to request the Sametime servlet URL. If a request makes it to the URL successfully, the servlet will return a code 200 and open the Sametime client chat with the user.  
   // We use the Instant.handleResponse() function as a callback to process the JSONP data object that the servlet hands back.  
   $.ajax({  
     type: 'GET',  
     url: getUrl,  
     dataType: 'jsonp',  
     jsonpCallback: 'Instant.handleResponse'  
   });  
 };  
   
 // A rudimentary callback function to handle JSONP data objects retrieved by the Instant.launchSametimeConversation() function.  
 Instant.handleResponse = function (data) {  
   if (data.returnCode === 200) {  
     alert('Success!');  
   } else {  
     alert('Failed!');  
   }  
 };  

In the functions above, you an see that we follow the same approach initially as the bundled sametime_invoke function, but stray heavily once we are staged up.

First, since we are always using chat, and never using the custom parameters, we strip the function parameters down to just stid (the username) of the user we are chatting with. We bundle the rest of this into our getUrl variable, along with the timestamp. We did add something new though, you'll notice the '&jsonp=Instant.handleResponse' parameter at the end of the URL. This specifies our callback function to the servlet. Normally, jQuery handles this fairly well, but the Sametime servlet we are posting against seems fairly specific with how this needs to be formed, so we include it in the URL. We also include it into the jQuery $.ajax method.

Our callback function is very simple for this example. It could be made much more complex though, simply by providing extra checks for different return codes, and added functionality based off of those checks.

After rebuilding this function, and adding the callback function, we are able to have much more control over how we launch the Sametime client window, and are able to check for failures finally. This function also consistently launches the Sametime client chat window when expected and hasn't failed to do so yet. The only two requirements are to have a Sametime client logged in, and to have the jQuery library linked in to the page that will house the above functions.

I'll post screenshots of the success below, as well as a GitHub gist of the code. Reach out to me at zclancy@instant-tech.com with any questions/comments.



1 comment:

Anonymous said...

Hey can you also send messages via Jquery to Sametime - if yes can you post your code ?