A lot of professional sites are now including a means of real time chat as part of their helpdesk sites. MSDN has their online concierge service, Dell has an online help, AT&T etc. So how do you do this type of thing within your own web site?
Well thanks to the Windows Live Web Messenger API it's actually quite easy to do.
The code in this article is actually very similar to the code in the previous articles I've posted and so I won't be going over all the functions again, instead I will point out the differences between this application and the previous one.
As you can see, the site structure is exactly the same. The Privacy.htm and Channel.htm required files are present. There are a few graphics files, a theme and everything else is contained in the Default.aspx file.
The main body of the html code found in Default.aspx is the same as in my previous articles however we've removed a couple of features as these aren't strictly necessary for a helpdesk application :-
As you will notice there no longer is a placeholder to change your online status and I've also removed the placeholder that allowed you to change your personal message. The rest however is the same.
The basis of this program is that you go to a specific page on a site if you are having a problem and chat to someone from the site to help solve your problem. You want this to all be automatic and for your user to do as little as possible to enable this. With the upcoming launch of Windows Live Messenger 9, this actually becomes scalable to a point as Messenger 9 allows for multiple presences on the same Live ID. Therefore you could have multiple team members, all sign into the same Live ID and all handling chats at the same time.
To make this as transparent as possible to the end user, once the user logs in with their Windows Live ID, you should check to see whether or not your pre-defined Help Desk Live ID is currently present in the users contact list, if it is great, pre-select that ID and the user can start chatting with you. If not then you will need to add your Help Desk Live ID transparently and select it so that the user can start a conversation with you. All of this is actually quite easy to accomplish.
The user comes along to your site where you have your Live Web Messenger application and is prompted to sign-in.
This is the same process and code as in the previous article so I won't regurgitate it here. Basically they are taken away from your site to the Windows Live login site, they enter their credentials and are then taken back to your site. The sign-in screen actually gets presented to them as a pop-up window so as not to completely take them away from your site.
You application changes to reflect the fact that the user is signing in. At this point the application is awaiting the token back from the Windows Live Login site saying that the user is authenticated.
As you will notice from the above screen shot, we don't display all of the users contacts, we only display the help desk contact. Why do we even want to display this? Well it's helpful to see whether someone is actually signed on and manning the online help desk or not, as is reflected in the status displayed next to the contact (in the above example the person is not currently online).
So what changes in the code do we need from my previous articles to get this to work? Basically it all happens in the displayContacts() routine :-
function displayContacts()
{ var sb = new StringBuilder();
var HelpDeskIndex = null;
_addressList = new Array(_user.get_contacts().get_count());
var groupList = new Array(_user.get_groups().get_count());
var enum2 = _user.get_groups().getEnumerator();
var enum1 = _user.get_contacts().getEnumerator();
var groupindex = 0;
index = 0;
while (enum2.moveNext())
{ var c = enum2.get_current();
var _contacts = new Array(c.get_contacts().get_count());
var enum3 = c.get_contacts().getEnumerator();
while (enum3.moveNext())
{ var contact = enum3.get_current();
var address = contact.get_currentAddress();
var dispName = address.get_presence().get_displayName();
var currAddress = address.get_address();
var status = Enum.toString(Microsoft.Live.Messenger.PresenceStatus, address.get_presence().get_status());
//Check to see if Helpdesk account is present
if(currAddress.toLowerCase() == "Your helpdesk address")
{ HelpDeskAdded = true;
var statusimg = null;
switch(status)
{ case "online":
statusimg = "<img src='online.gif' class='ContactImage' alt='online' />";
break;
case "offline":
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
case "appearOffline":
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
case "away":
statusimg = "<img src='away.gif' class='ContactImage' alt='away' />";
break;
case "beRightBack":
statusimg = "<img src='away.gif' class='ContactImage' alt='beRightBack' />";
break;
case "busy":
statusimg = "<img src='busy.gif' class='ContactImage' alt='busy' />";
break;
case "idle":
statusimg = "<img src='away.gif' class='ContactImage' alt='idle' />";
break;
case "inACall":
statusimg = "<img src='busy.gif' class='ContactImage' alt='InACall' />";
break;
case "outToLunch":
statusimg = "<img src='away.gif' class='ContactImage' alt='OutToLunch' />";
break;
default:
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
}
var statusLine = '';
var strDelete = '';
_addressList[index] = address;
if (dispName !== '')
{ statusLine = '<a href=\'BLOCKED SCRIPTcreateConv(' + index + ')\'>' + statusimg + dispName + '</a>'; }
else
{ statusLine = '<a href=\'BLOCKED SCRIPTcreateConv(' + index + ')\'>' + statusimg + currAddress + '</a>'; }
sb.append(statusLine);
HelpDeskIndex = index;
}
index++;
}
groupindex++;
index = 0;
while (enum1.moveNext())
{ var contact = enum1.get_current();
var address = contact.get_currentAddress();
var dispName = address.get_presence().get_displayName();
var currAddress = address.get_address();
var status = Enum.toString(Microsoft.Live.Messenger.PresenceStatus, address.get_presence().get_status());
if(currAddress.toLowerCase() == "Your helpdesk address")
{ HelpDeskAdded = true;
//Change status to an image
var statusimg = null;
switch(status)
{ case "online":
statusimg = "<img src='online.gif' class='ContactImage' alt='online' />";
break;
case "offline":
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
case "appearOffline":
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
case "away":
statusimg = "<img src='away.gif' class='ContactImage' alt='away' />";
break;
case "beRightBack":
statusimg = "<img src='away.gif' class='ContactImage' alt='beRightBack' />";
break;
case "busy":
statusimg = "<img src='busy.gif' class='ContactImage' alt='busy' />";
break;
case "idle":
statusimg = "<img src='away.gif' class='ContactImage' alt='idle' />";
break;
case "inACall":
statusimg = "<img src='busy.gif' class='ContactImage' alt='InACall' />";
break;
case "outToLunch":
statusimg = "<img src='away.gif' class='ContactImage' alt='OutToLunch' />";
break;
default:
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
}
var statusLine = '';
var strDelete = '';
_addressList[index] = address;
if (dispName !== '')
{ statusLine = '<a href=\'BLOCKED SCRIPTcreateConv(' + index + ')\'>' + statusimg + dispName + '</a>'; }
else
{ statusLine = '<a href=\'BLOCKED SCRIPTcreateConv(' + index + ')\'>' + statusimg + currAddress + '</a>'; }
sb.append(statusLine);
HelpDeskIndex = index;
}
index++;
}
groupindex++;
}
document.getElementById('divContacts').innerHTML = sb.toString(); if(HelpDeskAdded == true)
{ createConv(HelpDeskIndex);
}
else
{ alert(HelpDeskAdded);
var helpDeskContact = "Your helpdesk address";
if(_user !== null)
{ alert(_user);
_user.addContact(helpDeskContact, "Please accept in order to converse with the helpdesk", null);
HelpDeskAdded = true;
}
}
}
Overall this routine is very similar to the displayContacts() routine explained thoroughly in my previous articles however there are a few changes and also a hack (workaround) to a problem I came across, which I'll get to in a minute.
So first we get a list of all the contacts that the user has in their contacts list. Then we loop through each of these contacts to determine whether your helpdesk Live ID is present :-
Simply replace "your helpdesk address" with your help desks live id. Make sure however that it is all in lower case as you'll see from the line of code that that's how the code does the comparison.
If the contact is found then we get the contacts status and create a string to display their information in the contacts section at the bottom of the application.
Now you'll notice from the code that this basic routine looks like it's present twice. This is actually the quick hack. The reason for this is the way Live Contacts can be stored. Most contacts are actually assigned to a group and you may have multiple groups with multiple contacts in each. However some contacts are actually stored outside of groups and are in effect free floating. This hack gets around that problem. The first routine searches through all the contacts that belong to groups, the second section (which is essentially the same code) searches through the contacts that aren't assigned to any group.
At the bottom we have the check to see whether the help desk contact has been added or not. If it's already present then we are ready to start a conversation, if not then we need to add the help desk contact to the users contact list. The HelpDeskAdded variable is a global variable defined later in the code. And that's basically it.
One last point, I came across a bug in my previous articles. Before I had the :-
placed within the loop, this actually caused problems when trying to chat with the correct contact. This has now been moved outside of the loop.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>WLMessenger Site</title>
<script src="http://settings.messenger.live.com/api/1.0/messenger.js" type="text/javascript" language="javascript"></script>
<script type="text/javascript" language="javascript">
function scriptMain() { var privUrl = 'http://adg-cec322a.ad.uab.edu/WLMessengerSite/Privacy.htm';
var chanUrl = 'http://adg-cec322a.ad.uab.edu/WLMessengerSite/Channel.htm';
_signin = new Microsoft.Live.Messenger.UI.SignInControl('signinframe', privUrl, chanUrl, 'en-US'); _signin.add_authenticationCompleted(Delegate.create(null, authenticationCompleted));
}
function authenticationCompleted(sender, e) { _user = new Microsoft.Live.Messenger.User(e.get_identity());
_user.add_signInCompleted(Delegate.create(null, signInCompleted));
_user.signIn(null);
}
function signOutCompleted(sender, e)
{ _user = null;
_signin = null;
_addressList = null;
_contactCollection = null;
_conv = null;
_convSink = null;
convArray = null;
ContactsOpen = true;
HelpDeskAdded = false;
displayUserInfo();
displayContacts();
displayConversations();
}
function signInCompleted(sender, e) { if (e.get_resultCode() === Microsoft.Live.Messenger.SignInResultCode.success) { _user.get_presence().add_propertyChanged(Delegate.create(null, user_Presence_PropertyChanged));
displayUserInfo();
_addressList = new Array(_user.get_contacts().get_count());
var enum1 = _user.get_contacts().getEnumerator();
while (enum1.moveNext()) { var c = enum1.get_current();
var address = c.get_currentAddress();
address.get_presence().add_propertyChanged(Delegate.create(null, presence_PropertyChanged));
}
displayContacts();
_contactCollection = _user.get_contacts();
_user.get_conversations().add_propertyChanged(Delegate.create(null, conversation_collectionChanged));
}
}
/* Called by the Messenger Library on remote sign-out (i.e. user logged into Messenger on another computer). */
function onSignedOutRemotely(sender, e) { alert('You have been signed out remotely.'); }
function conversation_collectionChanged(sender,e) { displayConversations();
}
/* Called by the Messenger Library when presence status changes occur (i.e. contacts sign in or out). */
function presence_PropertyChanged(sender, e) { displayContacts();
}
/* Called by the Messenger Library when status changes of the user occur. */
function user_Presence_PropertyChanged(sender, e) { displayUserInfo();
if (_user.get_presence().get_status() === Microsoft.Live.Messenger.PresenceStatus.offline) { document.getElementById("divContacts").innerHTML = ""; document.getElementById("txtConv").innerHTML = ""; document.getElementById('btnSend').disabled = true; }
}
/* Populate the user information string. */
function displayUserInfo() { var userInfo = document.getElementById('userInfo'); var userAddress = _user.get_address().get_address();
var userDispName = _user.get_presence().get_displayName();
var userPersonalMessage = _user.get_presence().get_personalMessage();
var userStatus = Enum.toString(Microsoft.Live.Messenger.PresenceStatus, _user.get_presence().get_status());
var statusLine = document.createElement('p'); removeChildrenFromNode('userInfo'); if (userDispName !== '') { statusLine.appendChild(document.createTextNode(userDispName + ' (' + userAddress + '): ' + userStatus)); }
else { statusLine.appendChild(document.createTextNode(userAddress + ': ' + userStatus));
}
userInfo.appendChild(statusLine);
}
function displayContacts()
{ var sb = new StringBuilder();
var HelpDeskIndex = null;
_addressList = new Array(_user.get_contacts().get_count());
var groupList = new Array(_user.get_groups().get_count());
var enum2 = _user.get_groups().getEnumerator();
var enum1 = _user.get_contacts().getEnumerator();
var groupindex = 0;
index = 0;
while (enum2.moveNext())
{ var c = enum2.get_current();
var _contacts = new Array(c.get_contacts().get_count());
var enum3 = c.get_contacts().getEnumerator();
while (enum3.moveNext())
{ var contact = enum3.get_current();
var address = contact.get_currentAddress();
var dispName = address.get_presence().get_displayName();
var currAddress = address.get_address();
var status = Enum.toString(Microsoft.Live.Messenger.PresenceStatus, address.get_presence().get_status());
//Check to see if Helpdesk account is present
if(currAddress.toLowerCase() == "hackersoft@live.com")
{ HelpDeskAdded = true;
var statusimg = null;
switch(status)
{ case "online":
statusimg = "<img src='online.gif' class='ContactImage' alt='online' />";
break;
case "offline":
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
case "appearOffline":
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
case "away":
statusimg = "<img src='away.gif' class='ContactImage' alt='away' />";
break;
case "beRightBack":
statusimg = "<img src='away.gif' class='ContactImage' alt='beRightBack' />";
break;
case "busy":
statusimg = "<img src='busy.gif' class='ContactImage' alt='busy' />";
break;
case "idle":
statusimg = "<img src='away.gif' class='ContactImage' alt='idle' />";
break;
case "inACall":
statusimg = "<img src='busy.gif' class='ContactImage' alt='InACall' />";
break;
case "outToLunch":
statusimg = "<img src='away.gif' class='ContactImage' alt='OutToLunch' />";
break;
default:
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
}
var statusLine = '';
var strDelete = '';
_addressList[index] = address;
if (dispName !== '')
{ statusLine = '<a href=\'BLOCKED SCRIPTcreateConv(' + index + ')\'>' + statusimg + dispName + '</a>'; }
else
{ statusLine = '<a href=\'BLOCKED SCRIPTcreateConv(' + index + ')\'>' + statusimg + currAddress + '</a>'; }
sb.append(statusLine);
HelpDeskIndex = index;
}
index++;
}
groupindex++;
index = 0;
while (enum1.moveNext())
{ var contact = enum1.get_current();
var address = contact.get_currentAddress();
var dispName = address.get_presence().get_displayName();
var currAddress = address.get_address();
var status = Enum.toString(Microsoft.Live.Messenger.PresenceStatus, address.get_presence().get_status());
if(currAddress.toLowerCase() == "hackersoft@live.com")
{ HelpDeskAdded = true;
//Change status to an image
var statusimg = null;
switch(status)
{ case "online":
statusimg = "<img src='online.gif' class='ContactImage' alt='online' />";
break;
case "offline":
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
case "appearOffline":
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
case "away":
statusimg = "<img src='away.gif' class='ContactImage' alt='away' />";
break;
case "beRightBack":
statusimg = "<img src='away.gif' class='ContactImage' alt='beRightBack' />";
break;
case "busy":
statusimg = "<img src='busy.gif' class='ContactImage' alt='busy' />";
break;
case "idle":
statusimg = "<img src='away.gif' class='ContactImage' alt='idle' />";
break;
case "inACall":
statusimg = "<img src='busy.gif' class='ContactImage' alt='InACall' />";
break;
case "outToLunch":
statusimg = "<img src='away.gif' class='ContactImage' alt='OutToLunch' />";
break;
default:
statusimg = "<img src='offline.gif' class='ContactImage' alt='offline' />";
break;
}
var statusLine = '';
var strDelete = '';
_addressList[index] = address;
if (dispName !== '')
{ statusLine = '<a href=\'BLOCKED SCRIPTcreateConv(' + index + ')\'>' + statusimg + dispName + '</a>'; }
else
{ statusLine = '<a href=\'BLOCKED SCRIPTcreateConv(' + index + ')\'>' + statusimg + currAddress + '</a>'; }
sb.append(statusLine);
HelpDeskIndex = index;
}
index++;
}
groupindex++;
}
document.getElementById('divContacts').innerHTML = sb.toString(); if(HelpDeskAdded == true)
{ createConv(HelpDeskIndex);
}
else
{ alert(HelpDeskAdded);
var helpDeskContact = "Hackersoft@live.com";
if(_user !== null)
{ alert(_user);
_user.addContact(helpDeskContact, "Please accept in order to converse with the helpdesk", null);
HelpDeskAdded = true;
}
}
}
function createConv(index) { var newConv = _user.get_conversations().create(_addressList[index]);
var convId = _user.get_conversations().get_count();
switchConv(newConv.convId);
}
/* Send a message in the current conversation. */
function sendMsg() { var txtMessage = document.getElementById('txtMessage'); var messageText = txtMessage.value;
var message = new Microsoft.Live.Messenger.TextMessage(messageText, null);
if (_user) { _conv.sendMessage(message, null);
}
displayMsg(message);
txtMessage.value = '';
txtMessage.focus();
}
/* Called by the Messenger Library when a new message is received. */
function recvMsg(sender, e) { var message = e.get_message();
displayMsg(message);
document.getElementById('msgLastRecv').innerText = 'Last message at: ' + _conv.get_history().get_lastReceived().toString();}
/* Show text for a received message in the conversation window. */
function displayMsg(message) { var elMsg = message.createTextElement();
var txtConv = document.getElementById('txtConv'); var userName = "";
if (message.get_sender().get_presence().get_displayName() != null)
userName = message.get_sender().get_presence().get_displayName();
else
userName = message.get_sender().get_address();
txtConv.appendChild(document.createTextNode(userName + ' says: '));
txtConv.appendChild(elMsg);
txtConv.appendChild(document.createElement('br'));}
function displayConversations() { convArray = new Array();
var sb = new StringBuilder('<p><b>Active Conversations: (click a conversation to resume):</b></p>'); var item = 0;
var enum1 = _user.get_conversations().getEnumerator();
while (enum1.moveNext()) { var c = enum1.get_current();
convArray.push(c + ":" + item);
if (c.get_closed())
continue;
sb.append(convLink(c, item));
sb.append("<hr />"); item++;
}
document.getElementById('divConversations').innerHTML = sb.toString();}
function convLink(c, item)
{ var roster = c.get_roster();
var enum1 = roster.getEnumerator();
var names = new Array();
while (enum1.moveNext())
{ var dispName = enum1.get_current().get_presence().get_displayName();
var dispEmail = enum1.get_current().get_address();
if (dispName !== '') { names.push(dispName);
} else { names.push(dispEmail);
}
}
var sb = new StringBuilder();
sb.append('<a href=\"BLOCKED SCRIPTswitchConv(' + item + ')\">'); if (c == _conv)
sb.append('<b>'); sb.append(names.join(', ')); if (c == _conv)
sb.append('</b>'); sb.append('</a>'); sb.append(' '); sb.append('<a href=\"BLOCKED SCRIPTcloseConv(' + item + ')\">'); sb.append('close</a>'); return sb.toString();
}
function switchConv(id)
{ var c = _user.get_conversations().get_item(id);
if (c)
{ if (_conv) { _conv.remove_messageReceived(_convSink);
}
_convSink = Delegate.create(null, recvMsg);
_conv = c;
_conv.add_messageReceived(_convSink);
removeChildrenFromNode('txtConv'); /* Display all messages from the conversation history. */
var hist = c.get_history();
var histEnum = hist.getEnumerator();
while (histEnum.moveNext()) { displayMsg(histEnum.get_current());
}
document.getElementById('btnSend').disabled = false;}
displayConversations();
document.getElementById('txtMessage').focus();}
/* Close the current conversation. */
function closeConv(id) { var c = _user.get_conversations().get_item(id);
convArray.splice(id, 1);
c.close();
if (c == _conv)
{ removeChildrenFromNode('txtConv'); }
displayConversations();
if(_user.get_conversations().get_count() == 0)
document.getElementById('btnSend').disabled = true;}
/* Clear all children from the specified node
(i.e. clear messages from the conversation window). */
function removeChildrenFromNode(id){ var node = document.getElementById(id);
if(node == undefined || node == null)
{ return;
}
var len = node.childNodes.length;
while (node.hasChildNodes())
{ node.removeChild(node.firstChild);
}
}
function ToggleContacts()
{ var ContactImg = document.getElementById("ContactsExpand"); var Contacts = document.getElementById("divContacts"); if(ContactsOpen == true)
{ ContactImg.src = "plus_icon.gif";
ContactsOpen = false;
Contacts.style.display = 'none';
}
else
{ ContactImg.src = "minus_icon.gif";
ContactsOpen = true;
Contacts.style.display = 'block';
}
}
function ToggleContactGroup(elm)
{ var divtag = document.getElementById(elm);
var imgtag = "ContactsExpand" + elm;
var groupimg = document.getElementById(imgtag);
var lastslash = groupimg.src.lastIndexOf("/"); var imgcompare = groupimg.src.substr(lastslash + 1, groupimg.src.length - lastslash);
if(imgcompare == "minus_icon.gif")
{ groupimg.src = "plus_icon.gif";
divtag.style.display = 'none';
}
else
{ groupimg.src = "minus_icon.gif";
divtag.style.display = 'block';
}
}
_user = null;
_signin = null;
_addressList = null;
_contactCollection = null;
_conv = null;
_convSink = null;
convArray = null;
ContactsOpen = true;
HelpDeskAdded = false;
</script>
</head>
<body onload="scriptMain()">
<form id="form1" runat="server">
<div