UCWA by the numbers - #4 Anonymous Meeting Join

It has been some time since the overview of Communication (Conversation/Conferencing) which gave information on some of the basic tasks: sendMessage, setIsTyping, stopMessaging, and addParticipant. One of the big features of UCWA (as of CU1) is Anonymous Meeting Join as it can provide plugin-less web chat between an authenticated Lync User and a user browsing your website. In this journey through Anonymous Meetings I plan to cover authenticating to UCWA via a meeting Uri, handling some of the more interesting meeting events, and renewing the anonymous user's OAuth token.

What is a meeting Uri?

To me a meeting Uri appears in two distinctive forms:

  • joinUrl = https://meet.domain.com/user_name/unique_id - This can be found in meetings joined via the Lync Client via Meeting Entry Info -> Meeting Link or contained in a onlineMeeting created in UCWA
  • onlineMeetingUri = user_name@domain.com;gruu;opaque=app:conf:focus:id:unique_id - This can be found in an onlineMeeting created in UCWA

The idea is to take one of these (preferably onlineMeetingUri) and construct the Anonymous Meeting Grant Type (as seen at GettingStarted-Authentication). In the case where you only have a joinUrl bit of extra work is necessary and when crafting code to go along with this topic I created two functions to handle this as follows:

[code language="javascript" collapse="true"] // UcwaObject.js function determineDomain() { var domain = "";

if(meeting.joinUrl) { var temp = meeting.joinUrl.match(/*WordPress hates this regex...*/);

if(temp.length !== 0) { temp = temp[0];

domain = temp.slice(temp.indexOf(".") + 1); } } else if(meeting.onlineMeetingUri) { var temp = meeting.onlineMeetingUri.split("@");

if(temp.length >= 2) { temp = temp[1];

domain = temp.split(";")[0]; } }

return domain; }

obj.prototype.getOnlineMeetingUri = function() { if(meeting.onlineMeetingUri) { return meeting.onlineMeetingUri; } else if(meeting.joinUrl) { var domain = determineDomain(); var temp = meeting.joinUrl.split(domain);

if(temp.length >= 2) { temp = temp[1]; temp = temp.split("/");

return "sip:" + temp[1] + "@" + domain + ";gruu;opaque=app:conf:focus:id:" + temp[2]; } } } [/code]

WordPress is eating my regex above, but it can be viewed in the UcwaObject.js file later.

It is not defined in the above code but the meeting object can have a joinUrl and/or a onlineMeetingUri. The function determineDomain() will be used first by AutoDiscovery for xframe injection followed by cross-domain messaging. The idea of the function is to pull out the domain portion of either meeting piece either by regex matching (should match https://meet.domain.com above) with trimming or splitting on "@" followed by ";". The function getOnlineMeetingUri() will either return the meeting object's onlineMeetingUri or using determineDomain() with splitting to generate an onlineMeetingUri.

Authenticating Anonymously

I'm going to skip over AutoDiscovery as determineDomain() is used to get the domain from the supplied meeting object and it is the same as any other UCWA application. Authentication required a bit of change as the current samples (UCWA helper libraries) do not have a mechanism for handling an Anonymous Meeting Grant Type. By adding additional library variables, modifying the start() method to take and additional parameter, onlineMeetingUri, minor changes to requestAccessToken(), avoiding the request to makeMeAvailable, and exposing a method to get an authorization link (will be used later to renew authentication) it was possible to authenticate to the meeting.

[code language="javascript" collapse="true"] // Authentication.js ... // Link to the OAuth service this.authorizationLink = null; // The link to a meeting to authenticate to this.onlineMeetingUri = null; ...

obj.prototype.start = function(link, application, callback, onlineMeetingUri) { ... this.onlineMeetingUri = onlineMeetingUri; ... }

this.requestAccessToken = function(link, grantType, data) { this.authorizationLink = link; ... else if(this.onlineMeetingUri) { data = "grant_type=urn:microsoft.rtc:anonmeeting&password=" + this.onlineMeetingUri.slice(this.onlineMeetingUri.lastIndexOf(":") + 1) + "&ms_rtc_conferenceuri=" + this.onlineMeetingUri } ... }

// this.handleState() case 4: if(!scope.onlineMeetingUri) { scope.makeMeAvailable(); } else { scope.currentState++; scope.handleState(); }

obj.prototype.getAuthorizationLink = function() { return this.authorizationLink; } [/code]

Renewing Anonymous User

An anonymous user can participate in the meeting for about one hour before needing to renew their OAuth token. Renewing the OAuth token is handled by making a POST request to the OAuth token service and is why it made sense to store that authorization link. The one missing piece from the puzzle is a small update to Transport.js to provide a mechanism to get the accessToken. I created a method, getAuthorization(), to return both the accessToken and the tokenType.

[code language="javascript" collapse="true"] // Transport.js obj.prototype.getAuthorization = function() { return { accessToken: this.accessToken, tokenType: this.tokenType }; } [/code]

Creating an Anonymous Meeting Grant Type line and appending "&ms_rtc_renew=accessToken", it is possible to renew the existing OAuth token. I wrapped the code in an interval timer that should fire every 55 minutes extending that anonymous users OAuth token.

[code language="javascript" collapse="true"] // UcwaObject.js ... var onlineMeetingUri = _scope.getOnlineMeetingUri(); var grantLine = "grant_type=urn:microsoft.rtc:anonmeeting&password=" + onlineMeetingUri.slice(onlineMeetingUri.lastIndexOf(":") + 1) + "&ms_rtc_conferenceuri=" + onlineMeetingUri;

// Every 55 min we should attempt to extend our meeting lease... _timerId = window.setInterval(function() { _scope.Transport.clientRequest({ url: _scope.Authentication.getAuthorizationLink(), type: "post", contentType: "application/x-www-form-urlencoded;charset=UTF-8", data: grantLine += "&ms_rtc_renew=" + _scope.Transport.getAuthorization().accessToken }); }, 3300000); [/code]

Let's talk Events

Assuming everything worked for Authentication it is a good time to start listening to Events, but not without another modification to ensure all event data flows through to handlers.

[code language="javascript" collapse="true"] // Events.js // this.processEvents(data) ... scope.checkHrefOrOperationListeners(cachedData, data); ...

// this.checkHrefOrOperationListeners(cachedData, data) ... for(var i = 0; i < localHandlers.length; i++){ if(localHandlers[i] && localHandlers[i][normalizedEventType]){ var handler = localHandlers[i][normalizedEventType]; handler(cachedData, data); } } [/code]

The change in processEvents() is to allow more than just cacheData to flow through to checkHrefOrOperationListeners(). In checkHrefOrOperationListeners() I removed the check against data._embedded and the request going outbound to force the user to handle event processing on their own and because it was making a request for data when I really want to view the non-embedded data.

With those changes in place it was time to add event handlers for messaging, participant, localParticipant, and message. messaging events in the updated state help are used to know when to add modalities (messaging in this case) and when that modality has connected which in turn enables sendMessage, stopMessaging, and setIsTyping for the anonymous user. participant events are used to determine when users are typing (by checking data.in.rel === "typingParticipants") and when they are added to the meeting for the started handler while those same events in the completed state indicate a user has finished typing or has left the meeting. localParticipant events in the started state indicate joining the meeting and when in the completed state it means leaving the meeting. message events in the completed state indicate a user has successfully posted a message to the meeting and it can be processed.

I have to join the meeting too?

Authenticating to the meeting anonymously does not actually place the user into the meeting. The additional step is to make a POST request to joinOnlineMeeting with: anonymousDisplayName and onlineMeetingUri. After joining the meeting a torrent of events will flow containing who is currently in the meeting, who is typing, and other meeting vitals. Processing those events should put the UCWA application in a state where the anonymous user should be able to communicate to the meeting and see updates.

And now for something different...

I have not provided any code previously in postings because the topics were building up to something and I think that Anonymous Meeting Join is as good time as any to provide a sample application bringing together the UCWA helper libraries and user-generated code. Without further ado, presenting the mess, Anonymous Meeting Join Sample (I save CSS/Html pretties for other projects):

The sample code allows a user to provide either a joinUrl or an onlineMeetingUri to join the meeting. Filling out either field and a name followed by clicking the Join Meeting button will attempt to anonymously join that meeting. I have noticed that sometimes users will get placed into a lobby and it will be the organizer's job to admit these users in. Upon join the interface will change to add the connected users below the user's Display Name and an messages will be placed in the section above the input/Send button area with a typing notification appearing slightly above the pair as seen below:

The code is structured as follows:

  • AnonMeeting.js - Logic for handling buttons (join online meeting, send message) and event handling
  • Authentication.js - Version based on Feb 2013 Samples with updates
  • AutoDiscovery.js - Version based on Feb 2013 Samples
  • Cache.js - Version based on Feb 2013 Samples
  • Events.js - Version based on Feb 2013 Samples with updates
  • GeneralHelper.js - Version based on Feb 2013 Samples
  • index.html - Main site
  • Transport.js - Version based on Feb 2013 Samples with updates
  • UcwaObject.js - Logic for handling AutoDiscovery, Authentication, and renewing Authentication

Sample code: AnonMeeting.7z Sample Fiddler Trace: AnonMeeting.saz

Next Up

The next steps are to create more sample UCWA applications and explain their workings.