UCIntegration

From Zimbra :: Wiki

Jump to: navigation, search

Contents

Overview

note: A license is required to enable the Voice tab for Unified Communications. Additionally the tab is controlled by setting zimbraFeatureVoiceEnabled to TRUE at the COS or user level.

Voice Mail Tab

The voice mail tab provides a list view of a user’s voice mails. Depending on the provider implementation it may also consist of missed calls, answered calls and placed calls.

The Voice Mail tab is populated by making a call to SearchVoiceRequest. More details can be found in soap-voice.txt.

Zimbra Voice Extension

We assume the reader has already installed ZimbraServer and knows how to restart the jetty server. If not please refer to ZimbraServer/docs/INSTALL-*.txt.

Directories needed from perforce

  • ZimbraVoice/*
  • ZimbraCustomerServices/*
  • Telephony/*

Code for new ZimbraVoice extension should go under Telephony/ directory. Examples: Telephony/Cisco, Telephony/Mitel. Mitel & Cisco are existing voice implementations under Telephony directory. Please refer to them for any info or help. ZimbraVoice extension --> "Telephony/{ZimbraVoice-Extension}" Directory structure should follow

  • Telephony/{ZimbraVoice-Extension}/ZimbraServer (Example: Telephony/Cisco/ZimbraServer)
  • Telephony/{ZimbraVoice-Extension}/docs/* (Example: Telephony/Cisco/docs)
  • Telephony/{ZimbraVoice-Extension}/build.xml (Example: Telephony/Cisco/build.xml)
  • Telephony/{ZimbraVoice-Extension}/ZimbraServer directory structure should follow:
  • Telephony/{ZimbraVoice-Extension}ZimbraServer/src/*
  • Telephony/{ZimbraVoice-Extension}ZimbraServer/conf/*
  • Telephony/{ZimbraVoice-Extension}ZimbraServer/build.xml
  • Telephony/{ZimbraVoice-Extension}/build.xml & Telephony/{ZimbraVoice-Extension}/ZimbraServer/build.xml can be copied from existing voice implementation and edited appropriately.

ZimbraVoice/src/java/com/zimbra/cs/voice/VoiceStore.java is the voice server extension. The voice store provider extension has to be a subclass of VoiceStore.java. The new voice store extension acts as a middle man between ZimbraWebClient and the Voice Server. Please check the existing voice store modules (CiscoVoiceStore.java, MitelVoiceStore.java) for more info on implementation.

ZimbraVoice/docs/soap-voice.txt details the available Voice SOAP APIs. All SOAP requests are directed to appropriate handler classes based on the element to class mapping in VoiceService.java and the classes take care of calling methods from voice store provider extension (subclass of VoiceStore.java)

How to add a new Voice Soap API?

To add new SOAP API, a Request & Response element is added to VoiceConstants.java and the request element is registered in VoiceService.java by mapping to a handler class. If the mapped class needs to call any new function, the function is added to VoiceStore.java and implemented in all other existing voice extensions. Response element is used to generate SOAP responses. Please refer to any existing voice module for more information regarding the new API implementation.

Usually the new API changes may impact other existing voice implementations, so its recommended to avoid making those changes as much as possible. The new API needs to be tested using "zmsoap" and ZimbraVoice/docs/soap-voice.txt needs to be updated.

External users or 3rd part audience can only implement code in their own voice store extensions but they don't have the ability to make changes to ZimbraVoice (VoiceStore.java) or add new APIs, although they can suggest changes to the internal code or implementations.

ZimbraVoice is an extension deployed under /opt/zimbra/lib/ext/voice and the voice store provider is also a server extension deployed under /opt/zimbra/lib/ext/{ZimbraVoice-extension}

Make sure to restart jetty server once the code is built and deployed in /opt/zimbra/lib/ext/ directory. Server uses ClassLoader to load the instance of VoiceStore.java as parent class which in-turn uses URLClassLoader to load the voice store provider class.

How authentication works

Zimbra user accounts are mapped to voice user accounts. Voice account authentication differs among different voice vendors. Authentication is a two step process, initial authentication is done with the Zimbra Server using Zimbra auth token and the secondary authentication is done with Voice server. Each and every call to Voice Server is authenticated.

The voice tab displaysreceived voice mails, call logs (dialed, received, missed) and Trash folder if supported by the voice vendor. When the user clicks the Voice Tab in Zimbra Web UI, it makes a GetVoiceInfoRequest SOAP call. GetVoiceInfoRequest is mapped to GetVoiceInfo.java handler class which calls "GetVoiceInfo::processPhones(...)". Operations handled in GetVoiceInfo::processPhones(..) are as follows:

  • calls VoiceStore::getInstance() method to load the voice store provider class (subclass of VoiceStore.java)
  • calls auth() implemented in voice store provider (sub class of VoiceStore.java). Auth() basically does authentication and updates VoiceAccountInfo object with VoiceAccount id, voice mail num & phone nums. etc, and other phone attributes (VoicePhone object) for each number (voice mail num, mobile num, desk num etc)
  • Updates the local cache with ZimbraAccount id and all the phone numbers accessible to it. Cache is used to check whether a phone number is accessible by the requested zimbra account.
  • loops around each phone number to extract phone specific details like count of unread voice messages, call log folder update etc (doesn't update call log here, just updates the folders supported) and adds these details to SOAP response (GetVoiceInfoResponse).

GetVoiceInfoResponse is returned back to Zimbra web client with the above information (data from Step 2 & 4) embedded into it. After getting GetVoiceInfoResponse, UI automatically makes a SearchVoiceRequest SOAP call for the voice mail number. SearchVoiceRequest is mapped to handler SearchVoice.java which checks whether the ZimbrAccount can access to voice mail number and then calls SearchVoice()::searchPhone() to extract voicemail specific attributes (VoiceItem object).

When the user clicks/Plays a unread voicemail, UI makes a VoiceMsgActionRequest soap call to mark the voicemail unread to read and then dispatches a servlet GET request to download the voice message. VoiceMsgActionRequest is mapped to the handler VoiceMsgAction.java which takes care of marking a message from read to unread.

ExtensionDispatcherServlet dispatches the request to VoiceContentHttpHandler::doGet() which intern calls VoiceContent.get() to retrieve the voice message from voice server. ZimbraVoice extension pipe lines the voicemail data back to WebUI, in other words ZimbraVoice Extension doesn't store any data on the server. The format of the voice message depends on the formats supported by voice mail server. UI uses same process to play a already read voice message except that it doesn't make any call to VoiceMsgActionRequest.

Similarly ZimbraVoice extension supports APIs for voice mail delete, retrieve calls logs (missed, dialed, missed), delete call logs, mark voice messages are unread, undelete a voice message from Trash/Delete, upload a recorded voice message etc. Please refer to ZimbraVoice/docs/soap-voice.txt for more info.

Click to Call

Providing click to call capability via a zimlet can be broken down into three areas.

Contact Card

Adding Click to Call icon to the contact card In order to add the click to call icon to the contact card you need to first register your zimlet as a subscriber:

function(emailZimlet) {
 MyClick2CallZimlet.prototype.onEmailHoverOver =
 function(emailZimlet) {
 this._addSlide(emailZimlet);
};

You can then add an icon to the contact card and register a callback for implementing functionality by adding a EmailToolTipSlide:

 MyClick2CallZimlet.prototype._addSlide =
 function(emailZimlet) {
    //Do not show "Call" slide if unauthorized to use UC feature
    if (!appCtxt.getSettings()._hasVoiceFeature()) {
        return;
    }
    var tthtml = this._getTooltipBGHtml(); //HTML
    var selectCallback =  new AjxCallback(this, this._handleSlideSelect);
    this._slide = new EmailToolTipSlide(tthtml, true, "Click2CallZimletIcon", selectCallback, "Click to call");
    emailZimlet.slideShow.addSlide(this._slide);
 };
 

Finally, your zimlet needs to implement onPhoneClicked:

 MyClick2CallZimlet.prototype.onPhoneClicked =
 function(phone) {
    if (!this.myFromPhoneDlg) {
        this.myFromPhoneDlg = new MyClick2CallFromPhoneDlg(this.getShell(), this,
            this._destinationsInZimbra);
    }
    this.MyFromPhoneDlg.toPhoneNumber = this.toPhoneNumber = phone;
    this.MyFromPhoneDlg.showDialog();
 };

Providing a click to call dialog

Now that you have your zimlet registered with the contact card, you’ll want to implement a dialog to display when the user clicks on your icon to make a call.

To create the dialog you can extend ZmDialog:

 MyClick2CallFromPhoneDlg = function(shell, parent) {
  this.zimlet = parent;
  this.toPhoneNumber = "";
  this._dialogView = new DwtComposite(appCtxt.getShell());
  this._dialogView.setSize(300, 125);
  DwtDialog.call(this, {
    parent: shell,
    className: "ZmClick2CallFromPhoneDlg",
    title: this.zimlet.getMessage("fromPhoneDlgTitle"),
    view: this._dialogView,
    standardButtons: [DwtDialog.NO_BUTTONS],
    mode: DwtBaseDialog.MODELESS
  });
  this._buttonDesc = {};
  this._isLoaded = false;
  this.RE = new RegExp("\\+?\\b\\d([0-9\\(\\)\\.\\s\\-]){8,20}\\d\\b");
 };
 MyClick2CallFromPhoneDlg.prototype = new ZmDialog; 
 MyClick2CallFromPhoneDlg.prototype.constructor = MyClick2CallFromPhoneDlg;

In order to use this dialog to make calls you’ll need to know the from & to. In cases like the contact card you’ll want to prefill the “to” number. You can do so with something like:

  var cardAttributes = this.emailZimlet && this.emailZimlet.contactCard && this.emailZimlet.contactCard.attribs;
 if (!cardAttributes) return;
 this.click2CallDlg.toPhoneNumber = this.zimlet.toPhoneNumber = cardAttributes.mobilePhone || cardAttributes.workPhone;

In order to get the “from” number(s) (e.g. show a drop down of the numbers available to the user), the request will be based on your UC provider. Here’s one example of issuing a GetVoiceInfoRequest to retrieve the phone numbers:

 MyClick2CallFromPhoneDlg.prototype._getVoiceInfoAndShowDlg =
 function() {
 var soapDoc = AjxSoapDoc.create("GetVoiceInfoRequest", "urn:zimbraVoice");
 var respCallback = new AjxCallback(this, this._handleResponseVoiceInfo);
 var respErrorCallback = new AjxCallback(this, this._handleErrorResponseVoiceInfo);
 var params = {
    soapDoc: soapDoc,
    asyncMode: true,
    noBusyOverlay: true,
    callback: respCallback,
    errorCallback: respErrorCallback
 };
 appCtxt.getAppController().sendRequest(params);
 this._gettingVoiceInfo = true;
 };
 

In this case the callback would parse the response for GetVoiceInfoResponse.phones.

Registering the click to call dialog with phone objects

In order to get your click to call dialog to be invoked when an email or contact has a phone number your zimlet needs to implement the following methods: 

 MyClick2CallZimlet.prototype.match = function(line, startIndex) {
  var re = this.RE;
  re.lastIndex = startIndex;
  var m = re.exec(line);
  if (!m) { return m; }
  var phone = m[0]
  if (phone.length > 10 &&
   phone[0] != "+"   &&
   !(AjxUtil.arrayContains(phone, " ")) &&
   !(AjxUtil.arrayContains(phone, ".")) &&
   !(AjxUtil.arrayContains(phone, "-"))) {
     return null;
    } 
    else {
     m[0] = phone;
     return m;
    }
  }
 }
 

Then handle your matches by implementing the clicked method:

MyClick2CallZimlet.prototype.clicked = function(myElement, toPhoneNumber) { 
 this.toPhoneNumber = toPhoneNumber; this._showFromPhoneDlg(); 
};

Your zimlet XML configuration should have something like:

<contentObject type="phone">
		<matchOn>
			<regex attrs="g">\+?\(?\b\d([0-9\(\)\.\s\-]){8,20}\d\b</regex>
		</matchOn>
	</contentObject>

Presence Integration

In Z8 presence information is integrated into the “Email Contact Details” zimlet and displayed on the contact card when a user hovers over an email address. In order to add presence to the email zimlet, you need to register your zimlet that is providing presence as a subscriber. The email zimlet will notify other zimlets that implement “onEmailHoverOver”. An example of setting up presence for your zimlet:

  MyPresenceZimlet.prototype.onEmailHoverOver = function(emailZimlet) { 
   emailZimlet.addSubscriberZimlet(this, false, {presenceCallback: this.getPresence.bind(this)}); 
 }

Your getPresence method then may make a REST call to retrieve the presence based on the email address which is being hovered. The presenceCallback function will be registered by the email zimlet and handle presence calls and caching.

Here's an example of what a getPresence method may look like:

ZmPresenceZimlet.prototype.getPresence =
function(presentity, presenceCallback) {

this.presenceAPI.getPresence(presentity, presenceCallback); //this makes a REST call to get the presence and then we parse the XML
};

The presenceCallback is UnknownPersonSlide.prototype._handlePresence. It requires a object with a property "status". We currently recognize the following status values and associate them with a colored icon representing the presence status:

"dnd", "vacation", "on-the-phone", "busy", "unavailable", "away", "available", "unknown"

Account Setup

The following are command line instructions for configuring a voice provider and mapping the provider to an end user.

  • Enable the uc provider
$ zmprov mcf zimbraUCProviderEnabled {provider_name}
  • Create the UC service and provider URLs for voicemail, webdialer and presence (if applicable)
$ zmprov cucs {service_name} zimbraUCProvider {provider_name}  zimbraUCVoicemailURL {voicemail_url} zimbraUCCallControlURL {webdialer_url} zimbraUCPresenceURL {presence_url}
  • Map the UC service to an end user
$ zmprov ma user1@`zmhostname` zimbraUCUsername ucUsername zimbraUCServiceId service-ID
  • Map the UC password to the end user
$ zmsoap -v -z -m user1@`zmhostname` -t voice ChangeUCPasswordRequest/password=ucAccount_password

If your UC system has a self signed SSL cert, you need to add support in your config and restart your server:

zmlocalconfig -e ssl_allow_accept_untrusted_certs=true
zmlocalconfig -e ssl_allow_mismatched_certs=true
zmlocalconfig -e ssl_allow_untrusted_certs=true
Personal tools