Skip to content
Branch: master
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
Images
Setup
Slides
Source
README.md
Setup.cmd

README.md

# Integrating Conversations with Skype and Office 365 Connectors #
## Overview ##

Office 365 now enables developers to build a powerful new generation of conversation-driven applications. Office 365 Connectors and Skype SDKs are two developer platforms to lighting up conversations in your applications. Office 365 Connectors allow 3rd party applications to send data into Office 365 and Outlook in near real-time. Users can subscribe to relevant data/applications and have informative and actionable "cards" display in Outlook. Skype SDKs allow developers to being the power capabilities of Skype into their applications built for the web and mobile platforms.

In this module, you will explore development with Office 365 Connectors and Skype SDKs, including how to integrate them into existing web applications.

### Objectives ### In this module, you'll see how to:
  • Manually register an Office 365 Connector and use a webhook to send data into Office 365.
  • Connect an existing web application to Office 365 via Office 365 Connectors, including the addition of "Connect to Office 365" buttons, handling callbacks from Office 365, and sending data into Office 365 via webhooks.
  • Skype-enable and existing web application using the Skype Web SDK.

Prerequisites

The following is required to complete this module:

Note: You can take advantage of the Visual Studio Dev Essentials subscription in order to get everything you need to build and deploy your app on any platform.

### Setup ### In order to run the exercises in this module, you'll need to set up your environment first.
  1. Open Windows Explorer and browse to the module's Source folder.
  2. Right-click Setup.cmd and select Run as administrator to launch the setup process that will configure your environment and install the Visual Studio code snippets for this module.
  3. If the User Account Control dialog box is shown, confirm the action to proceed.

Note: Make sure you've checked all the dependencies for this module before running the setup.

### Using the Code Snippets ###

Throughout the module document, you'll be instructed to insert code blocks. For your convenience, most of this code is provided as Visual Studio Code Snippets, which you can access from within Visual Studio 2015 to avoid having to add it manually.

Note: Each exercise is accompanied by a starting solution located in the Begin folder of the exercise that allows you to follow each exercise independently of the others. Please be aware that the code snippets that are added during an exercise are missing from these starting solutions and may not work until you've completed the exercise. Inside the source code for an exercise, you'll also find an End folder containing a Visual Studio solution with the code that results from completing the steps in the corresponding exercise. You can use these solutions as guidance if you need additional help as you work through this module.


## Exercises ## This module includes the following exercises:
  1. Building Office 365 Connectors
  2. Developing with the Skype Web SDK

Estimated time to complete this module: 60 minutes

Note: When you first start Visual Studio, you must select one of the predefined settings collections. Each predefined collection is designed to match a particular development style and determines window layouts, editor behavior, IntelliSense code snippets, and dialog box options. The procedures in this module describe the actions necessary to accomplish a given task in Visual Studio when using the General Development Settings collection. If you choose a different settings collection for your development environment, there may be differences in the steps that you should take into account.

Exercise 1: Building Office 365 Connectors

Office 365 Connectors are a great way to get useful information and content into your Office 365 Group. Any user can connect their group to services like Trello, Bing News, Twitter, etc., and get notified of the group's activity in that service. From tracking a team's progress in Trello, to following important hashtags in Twitter, Office 365 Connectors make it easier for an Office 365 group to stay in sync and get more done. Office 365 Connectors also provides a compelling extensibility solution for developers which we will explore in this Exercise.

In this Exercise, you will explore the developer options for working with Office 365 Connectors, including adding connectors, leveraging webhooks, and integrating Office 365 Connectors into existing web application via the "Connect to Office 365" button.

Task 1 - Getting Started with Office 365 Connectors

In this Task, you will explore Office 365 Groups and some of the existing Office 365 Connectors that are available.

  1. Open a browser and navigate to https://portal.office.com and sign-in with the Office 365 credentials that were provided to you.

  2. Office 365 Connectors for Groups are currently under developer preview. In order to access them, navigate to OWA using the URL https://outlook.office.com/owa/#path=/mail&EnableConnectorDevPreview=true

  3. Once you are signed into OWA, locate the Office 365 Groups you are a member of in the lower left navigation.

    Groups in OWA

    Groups in OWA

  4. Create your own unique group by clicking the + (plus) sign to the right of the Groups title in the left navigation.

    Add Group

    Add Group

  5. Provide a name and description for the new group and click Create (optionally add members once the groups has been created).

  6. Select More > Connectors from the Group's top navigation (if More isn't an option, you might try navigating back to the group using the link in Step 2).

    Connectors in group navigation

    Connectors in group navigation

  7. Explore some of the Office 365 Connectors that are available out of the box.

    OOTB Connectors

    OOTB Connectors

  8. Locate the Twitter connector and click Add.

  9. The Twitter connector requires you to sign-in with a Twitter account. To do this, click on the Log in button.

    Twitter Connector Log in

    Twitter Connector Log in

  10. After authorizing the Office 365 Connector for Twitter, you can select specific users/hashtags to follow and how frequently they show up in the Office 365 Group. Try to follow yourself or a hashtag.

    Configure Twitter Connector

    Configure Twitter Connector

  11. Post a tweet that matches the criteria in Step 10 and see it show up in the Office 365 Group.

    Twitter post via Office 365 Connector

    Twitter post via Office 365 Connector

Task 2 - Leveraging Webhooks with Office 365 Connectors

Hopefully Task 1 helped to illustrate the power of Office 365 Connectors, but did little to showcase the unique developer opportunity. In this Task, you will explore Office 365 Connector webhooks and how developer can leverage then to send data into Office 365.

  1. Navigate to the Office 365 Group you created in the previous Task and select More > Connectors from the Group's top navigation.

  2. Locate the Incoming Webhook Connector and click the Add button.

    Incoming Webhook

    Incoming Webhook

  3. Specify a name for the incoming webhook (ex: Build 2016) and click the Create button.

  4. The confirmation screen will display a URL that is the webhook end-point we will use later in this Task.

    Webhook Confirmation

    Webhook Confirmation

  5. Open a new browser tab and navigate to https://www.hurl.it, which is an in-browser web request composer similar to what Fiddler offers.

  6. When the page loads, add the following details:

    • Operation: POST
    • Destination Address: webhook URL from Step 4
    • Headers: Content-Type: application/json
    • Body: { "text": "Hello from Build 2016" }

    Manual Webhook

    Manual Webhook

  7. Accept the Captcha and click Launch Request. You should get a confirmation screen that looks similar to the following.

    Webhook Manual Confirmation

    Webhook Manual Confirmation

  8. If you return to the Office 365 Group, you should be able to locate the message you sent into it via the webhook.

    Message sent into Group via webhook

    Message sent into Group via webhook

  9. Although you sent a very simple message into the webhook, Office 365 Connectors support a much more complex message format. You can get more details on the message format by visiting https://dev.outlook.com/Connectors/GetStarted.

Task 3 - Integrating "Connect to Office 365" into Existing Applications

Task 2 had you manually register a webhook for an Office 365 Connector. In this Task, you will modify an existing web application to register webhooks with Office 365 by leveraging a "Connect to Office 365" button. You will capture the webhook details in a custom callback and send messages to Office 365 when new records are created in the application.

This Task uses a starter project to serve as the existing application. The application is a Craigslist-style selling site named BillsList. You are tasked with enhancing BillsList to allow users to subscribe to listing categories and send messages to Office 365 Groups when new listings match the subscription criteria.

  1. Open Windows Explorer and browse to the module's Source\Ex1\Begin folder.

  2. Double-click the solution file (BillsListASPNET.sln) to open the solution in Visual Studio Community 2015.

  3. The starter solution actually has two project...BillsListASPNET (the web application) and BillsListASPNET.Data (database project). Right-click the BillsListASPNET.Data project and select Publish.

  4. On the Publish Database dialog, click Edit to configure the connection information.

    Publish DB

    Publish DB

  5. On the Connection Properties dialog enter (localdb)\MSSQLLocalDB for the Server name and click OK.

    Connection Properties

    Connection Properties

  6. When you return to the Publish Database dialog, click the Publish button to publish the database to LocalDb.

  7. When the database has finished publishing, press F5 or start the debugger to test the starter project.

  8. When the application loads, click on Listings in the top navigation. This will prompt you to sign-in. Use the Office 365 account that was provided to you (it also supports Consumer/MSA accounts like outlook.com, live.com, hotmail.com, etc).

  9. The Listings view is one of the views we want to modify to support subscriptions to Office 365 Connectors. Notice that listings also have a category link, which is the second view we will add the "Connect to Office 365" button.

    Listings

    Listings

  10. Open a browser and navigate to https://outlook.office.com and sign-in with the Office 365 account that was provided to you.

  11. Once you are signed into Outlook, navigate to the Connectors Developer Dashboard https://outlook.office.com/connectors/publish. This is a special page that allows you to register 3rd party connectors and generate "Connect to Office 365" markup.

    Add Connectors screen

    Connectors Developer Dashboard

  12. Click on the New Connector and fill out the following details in the New Connector form in the Connectors Developer Dashboard.

  13. Save the Form to generate "Connect to Office 365" button markup in step 2 of the form. Copy this markup for use in subsequent steps.

    New Connector Form

  14. Close the browser to stop debugging and open the _Layout.cshtml file located in the web project at Views > Shared.

  15. After the second navbar-collapse element (between lines 27-28), add following markup as a container for the "Connect to Office 365" button.

     <div class="navbar-collapse collapse" style="padding-top: 5px;">
         <div class="nav navbar-nav navbar-right">
    
         </div>
     </div>	
    
  16. Next, paste the "Connect to Office 365" button markup generated in step 13 into the new div and wrap the entire thing in a authentication check (Request.IsAuthenticated). This will only show the button when the user is signed in.

     @if (Request.IsAuthenticated)
     {
     <div class="navbar-collapse collapse" style="padding-top: 5px;">
         <div class="nav navbar-nav navbar-right">
             <a href="https://outlook.office.com/connectors/Connect?state=myAppsState&app_id=a786cbb7-f80d-4968-91c0-9df7a96d75f0&callback_url=https://localhost:44300/callback"><img src="https://o365connectors.blob.core.windows.net/images/ConnectToO365Button.png" alt="Connect to Office 365"></img></a>  
         </div>
     </div>
     }
    
  17. Update the state URL parameter from myAppsState to @Request.Url.OriginalString

     <a href="https://outlook.office.com/connectors/Connect?state=@Request.Url.OriginalString&app_id=a786cbb7-f80d-4968-91c0-9df7a96d75f0&callback_url=https://localhost:44300/callback"><img src="https://o365connectors.blob.core.windows.net/images/ConnectToO365Button.png" alt="Connect to Office 365"></img></a> 
    
  18. You might recall we are passing in a callback location of https://localhost:44300/callback to Office 365. However, the Callback controller does not yet exist...let's create it. Right click the Controllers folder in the web project and select Add > Controller.

  19. Select MVC Controller - Empty for the controller type and name it CallbackController.

    New Controller

    New Controller

  20. Inside the CallbackController class, add the o365-callbackctrl code snippet by typing o365-callbackctrl and pressing tab.

(Code Snippet - o365-callbackctrl)

       // GET: Callback
       public ActionResult Index()
       {
           var error = Request["error"];
           var state = Request["state"];
           if (!String.IsNullOrEmpty(error))
           {
               return RedirectToAction("Error", "Home", null);
           }
           else
           {
               var group = Request["group_name"];
               var webhook = Request["webhook_url"];
               Subscription sub = new Subscription();
               sub.GroupName = group;
               sub.WebHookUri = webhook;

               //save the subscription
               using (BillsListEntities entities = new BillsListEntities())
               {
                   entities.Subscriptions.Add(sub);
                   entities.SaveChanges();
                   return Redirect(state);
               }
           }
       }
  1. You may need to resolve the following reference after adding the above snippet.

     using BillsListASPNET.Models;
    
  2. The controller looks for information returned from Office 365 and saves it as a subscription. The specific information passed from Office 365 as parameters include:

    • error: error details if the connection with Office 365 failed (ex: user rejected the connection)
    • state: the state value that was passed in via the "Connect to Office 365" button. In our case, it could include the category the user subscribed to
    • group_name: the name of the group the user selected to connect to
    • webhook_url: the webhook end-point our application will use to send messages into Office 365
  3. Almost done, just need to update the Create activity to send messages to the appropriate webhooks. Open the ItemsController.cs file located in the Controllers folder of the web project.

  4. Towards the bottom of the class, add the o365-callwebhook code snippet by typing o365-callwebhook and pressing tab.

(Code Snippet - o365-callwebhook)

       private async Task callWebhook(string webhook, Item item)
       {
           var imgString = "https://billslist.azurewebsites.net/images/logo_40.png";
           if (Request.Files.Count > 0)
           {
               //resize the image
               Request.Files[0].InputStream.Position = 0;
               Image img = Image.FromStream(Request.Files[0].InputStream);
               var newImg = (Image)(new Bitmap(img, new Size(40, 40)));

               //convert the stream
               using (var stream = new System.IO.MemoryStream())
               {
                   newImg.Save(stream, ImageFormat.Jpeg);
                   stream.Position = 0;
                   var bytes = new byte[stream.Length];
                   stream.Read(bytes, 0, bytes.Length);
                   imgString = "data:image/jpg;base64, " + Convert.ToBase64String(bytes);
               }
           }

           //prepare the json payload
           var json = @"
               {
                   'summary': 'A new listing was posted to BillsList',
                   'sections': [
                       {
                           'activityTitle': 'New BillsList listing',
                           'activitySubtitle': '" + item.Title + @"',
                           'activityImage': '" + imgString + @"',
                           'facts': [
                               {
                                   'name': 'Category',
                                   'value': '" + item.Category + @"'
                               },
                               {
                                   'name': 'Price',
                                   'value': '$" + item.Price + @"'
                               },
                               {
                                   'name': 'Listed by',
                                   'value': '" + item.Owner + @"'
                               }
                           ]
                       }
                   ],
                   'potentialAction': [
                       {
                           '@context': 'http://schema.org',
                           '@type': 'ViewAction',
                           'name': 'View in BillsList',
                           'target': [
                               'https://localhost:44300/items/detail/" + item.Id + @"'
                           ]
                       }
                   ]}";

           //prepare the http POST
           HttpClient client = new HttpClient();
           client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
           var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
           using (var response = await client.PostAsync(webhook, content))
           {
               //TODO: check response.IsSuccessStatusCode
           }
       }
  1. You will likely need to resolve a number of references after adding the above snippet.

     using System.Net.Http;
     using System.Net.Http.Headers;
     using System.Threading.Tasks;
     using System.Drawing;
     using System.Drawing.Imaging;
    
  2. The snippet takes the new listing details and sends it to Office 365 via POST to the webhook end-point.

  3. Finally, locate the Create activity within the class. Create is overloaded, so select the one that is marked with HttpPost and has the Item parameter. Inside the using statement add the code below between SaveChanges() of the new listing and the RedirectToAction() statement. This identifies matching subscriptions and calls the appropriate webhooks.

       //save the item to the database
       using (BillsListEntities entities = new BillsListEntities())
       {
           entities.Items.Add(item);
           var id = entities.SaveChanges();

           //loop through subscriptions and call webhooks for each
           foreach (var sub in entities.Subscriptions)
           {
               await callWebhook(sub.WebHookUri, item);
           }

           return RedirectToAction("Detail", new { id = item.Id });
       }
  1. It's time to test your work. Press F5 or start the debugger to launch the application. When you click on Listings (and sign-in) the "Connect to Office 365" button should display in the header.

    Connect to Office 365 button in header

    Connect to Office 365 button

  2. Click on the "Connect to Office 365" button. You should be redirected to a screen to select a Office 365 Group to connect to.

    Select group to connect to

    Select group to connect to

  3. Select an Office 365 Group and click Allow to complete establish the connection with Office 365 and return to BillsList.

  4. To test the connection, click on My Listings and Create listing with the category you subscribed to. A Connector Card for the listing should almost immediately show up in the Office 365 Group.

    Connect message in group UI

    Connector card

Exercise 2: Developing with the Skype Web SDK

Skype is one of the most popular communication platforms in the world. Many organization look to Skype for Business to deliver their real-time communication needs. Skype for Business offers powerful SDKs to integrate real-time conversations into both web and mobile application.

In this exercise, you will convert and existing web application to integrate Skype presence and instant messaging with the Skype Web SDK. You will also see how to integrate additional conversation modalities like voice and video.

Task 1 - Setup

This exercise uses a starter solution that follows a help desk scenario. It displays help desk tickets assigned to the signed in user. Your job in the exercise is to integrate Skype presence and instant messaging into the help desk application. The starter solution is built with AngularJS and is already configured to authenticate against Azure AD and display the user's profile picture using the Microsoft Graph. Although it helps, you do not need prior experience with AngularJS, basic JavaScript and HTML skills will suffice.

In the first task, you will familiarize yourself with the starter solution and get it running locally.

  1. Open a command prompt and browse to the module's Source > Ex2 > Begin folder.

  2. Open the solution by typing code .

     code .
    
  3. The starter solution is considered an AngularJS single page application (SPA) because it uses single index.html page to host all the content. It leverages a Model/View/Controller model to load dynamic content in the single page. The list below lists some of the significant components of the starter solution.

    • index.html: the single html that will host all the applications content.
    • app: the folder containing all of the application logic and partial views/templates.
      • templates: contains all the HTML partial views for the application.
      • app.js: defines the primary Angular module for the application and the application routes.
      • factory.js: defines an Angular factory that provides properties and services across the application.
      • controllers.js: defines all the controllers for the application.
    • lib: the folder containing all the frameworks/dependent scripts (ex: Bootstrap, Angular, etc). All of these were imported using bower.
  4. You should be given two Office 365 accounts for this exercise. You should identify one as the Skype User and one as the Web User. The Skype User will sign into Skype for Business and the Web User will use the web application built in this exercise. Open the factory.js file in the app folder. Go to lines 32-33 and update the tenantDomain and skypeTestUser with the tenant domain and Skype User respectively. The example below updates these settings with the contoso tenant and john user.

       31		//Hack...will use hard-coded tickets for demo purposes
       32		var tenantDomain = "contoso.onmicrosoft.com"; //CHANGE THIS TO YOUR OFFICE 365 TENANT
       33		var skypeTestUser = "john@contoso.onmicrosoft.com"; //CHANGE THIS TO THE USER THAT WILL TEST FROM SKYPE
  1. Open the app.js file in the app folder. Notice how each route includes an attribute to determine if authentication is required or not. This is enabled through ADAL-Angular, which is the Azure Active Directory Authentication Library (ADAL) that manages authentication for the application.
       $routeProvider.when("/login", {
           controller: "loginCtrl",
           templateUrl: "/app/templates/view-login.html",
           requireADLogin: false
       }).when ("/tickets", {
           controller: "ticketsCtrl",
           templateUrl: "/app/templates/view-tickets.html",
           requireADLogin: true
       }).otherwise({
           redirectTo: "/login"
       });
  1. Locate the section of app.js where the ADAL settings are configured and update the tenant property with the tenant domain you are using in Office 365.
       adalProvider.init({
           instance: "https://login.microsoftonline.com/",
           tenant: "TENANT.onmicrosoft.com", //TODO: CHANGE THIS TO YOUR OFFICE 365 TENANT DOMAIN
           clientId: "6fd45769-7a1e-4dc5-a876-90fa781b3d3e",
           endpoints: {
               "https://webdir.online.lync.com": "https://webdir.online.lync.com",
               "https://graph.microsoft.com": "https://graph.microsoft.com"
           }
       }, $httpProvider);
  1. Open the controllers.js file in the app folder and locate the loginCtrl. Notice it's use of adalSvc to check if the user is authenticated.
       .controller("loginCtrl", ["$scope", "$location", "adalAuthenticationService", function($scope, $location, adalSvc) {
           if (adalSvc.userInfo.isAuthenticated) {
               $location.path("/tickets");
           }

           $scope.login = function() {
               adalSvc.login();  
           };
       }])
   ```

8. Return to the command prompt and type `superstatic --port 8000`. This will start a simple web server to host your client-side web application.
	superstatic --port 8000

9. Open a browser and navigate to **http://localhost:8000**. The site should direct you to the **login** view and prompt you to sign-in with **Office 365**.

   ![Sign-in with Office 365](Images/Mod4_signin.png?raw=true "Sign-in with Office 365")

    _Sign-in with Office 365_

10. Sign into the web application using the credentials of the Web User. Once you sign-in, the application should display a list of help desk tickets. In the next Task, you will modify this view to display Skype presence for each user.

   ![Help desk tickets](Images/Mod4_tickets.png?raw=true "Help desk tickets")

    _Help desk tickets_

<a name="Ex2Task2"></a>
#### Task 2 - Sign-in and Presence with Skype for Business ####

In this task, you will introduce the Skype Web SDK into the solution and use it to subscribe and display presence for users in the help desk application. Applications build against the Skype Web SDK need to be registered in Azure AD. We will demonstrate this process, but you will use a predefined application ID in this lab.

1. Create a **skype.js** file in the **app** folder of the solution and populate it with base scaffolding using the **o365-skypefactory** code snippet. This creates a **skype.services** Angular module and populates it with some of the core settings to integrate the **Skype Web SDK**. The **skypeSvc** factory will provide persistent objects and services across all controllers of the application. It uses a **singleton** pattern for defining properties a functions.

(Code Snippet - _o365-skypefactory_)

```JavaScript
       angular.module("skype.services", [])
       .factory("skypeSvc", ["$rootScope", "$http", "$q", function($rootScope, $http, $q) {
           var skypeSvc = {};

           //private properties
           var apiManager = null;
           var client = null;

           //config settings for the app
           skypeSvc.config = {
               apiKey: "a42fcebd-5b43-4b89-a065-74450fb91255", // SDK DF
               apiKeyCC: "9c967f6b-a846-4df2-b43d-5167e47d81e1", // SDK+CC DF
               initParams: {
                   auth: null,
                   client_id: "6fd45769-7a1e-4dc5-a876-90fa781b3d3e", //Client ID of app in Azure AD
                   cors: true,
                   origins: ["https://webdir.online.lync.com/autodiscover/autodiscoverservice.svc/root"],
                   redirect_uri: "/auth.html",
                   version: "sdk-samples/1.0.0" // this helps to identify telemetry generated by the samples
               }
           };

           //Add additional Skype logic here...signin, status, conversations, etc

           return skypeSvc;
       }]);
  1. Take note of the redirect_uri property above that is set the /auth.html. This is a page the Skype Web SDK will open in a hidden iFrame to help with token acquisition.

  2. Next, open the index.html file in the root of the project and add a script reference to the Skype Web SDK below the bootstrap reference (you can also use the o365-skyperef code snippet for this).

       <!-- JQuery and Bootstrap references -->
       <script type="text/javascript" src="lib/jquery/dist/jquery.min.js"></script>
       <script type="text/javascript" src="lib/bootstrap/dist/js/bootstrap.min.js"></script>

       <!-- Skype reference -->
       <script src="https://swx.cdn.skype.com/shared/v/latest/SkypeBootstrap.min.js"></script>
  1. You also need to add a reference to the skype.js Angular factory you created in step 1. Add that in the app scripts section between the factory.js and the controllers.js.
       <!-- App scripts -->
       <script type="text/javascript" src="app/factory.js"></script>
       <script type="text/javascript" src="app/skype.js"></script>
       <script type="text/javascript" src="app/controllers.js"></script>
       <script type="text/javascript" src="app/app.js"></script>
  1. Next, you need to inject a dependency of the skype.services module you created in step 1 in the main Angular module of the application. Open the app.js file and modify the helpdesk module definition as follows.
   	angular.module("helpdesk", ["skype.services", "helpdesk.services", "helpdesk.controllers", "ngRoute", "AdalAngular"])
  1. Return to the skype.js file you created in Step 1. In the "Add additional Skype logic here" section, add an ensureClient function on the skypeSvc object to initialize the Skype Web SDK. You can follow the code below or use the o365-skypeEnsureClient code snippet.

(Code Snippet - o365-skypeEnsureClient)

       //ensures the skype client object is initialized
       var ensureClient = function() {
           var deferred = $q.defer();

           if (client != null)
               deferred.resolve();
           else {
               Skype.initialize({
                   apiKey: skypeSvc.config.apiKeyCC
               }, function (api) {
                   apiManager = api;
                   client = apiManager.UIApplicationInstance;
                   client.signInManager.state.changed(function (state) {
                       $rootScope.$broadcast("stateChanged", state);
                   });
                   deferred.resolve();
               }, function (er) {
                   deferred.resolve(er);
               });
           }

           return deferred.promise;
       };
  1. The code in Step 6 ensures the Skype Web SDK is initialized, but you also need to ensure the user is signed in. Below ensureClient, add an ensureSignIn function to the skypeSvc object by using the o365-skypeEnsureSignIn code snippet. Notice that it checks the uses the client.signInManager to check the sign-in state and calls signIn if needed.

(Code Snippet - o365-skypeEnsureSignIn)

       //signs into skype
       skypeSvc.ensureSignIn = function() {
           var deferred = $q.defer();

           ensureClient().then(function() {
               //determine if the user is already signed in or not
               if (client.signInManager.state() == "SignedOut") {
                   client.signInManager.signIn(skypeSvc.config.initParams).then(function (z) {
                       //listen for status changes
                       client.personsAndGroupsManager.mePerson.status.changed(function (newStatus) {
                           console.log("logged in status: " + newStatus);
                       });

                       //In the future we can listen for new inbound conversations like this
                       client.conversationsManager.conversations.added(function (conversation) { });

                       //resolve the promise
                       deferred.resolve();
                   }, function (er) {
                       deferred.reject(er);
                   });
               }
               else {
                   //resolve the promise
                   deferred.resolve();
               }
           }, function(er) {
               deferred.reject(er);
           });

           return deferred.promise;
       }
  1. Also notice that the code snippet above includes a line to listen for new inbound conversations. We won't fully implement that in this lab, so this is show just as a future reference.
       //In the future we can listen for new inbound conversations like this
       client.conversationsManager.conversations.added(function (conversation) { });
  1. Next, you should create a subscribeToStatus function on the skypeSvc to check the status of user that is passed into it (queried using client.personsAndGroupsManager.createPersonSearchQuery). The function should also subscribe to status changes for the user. However, you don't want to subscribe the same user more than once, so keep track of subscriptions in a userSubs array. You can add this script block using the o365-skypeSubscribeUser code snippet.

(Code Snippet - o365-skypeSubscribeUser)

       //subscribes to the status of a user
       var userSubs = [];
       skypeSvc.subscribeToStatus = function(id) {
           var deferred = $q.defer();

           //query for the user by their id
           var query = client.personsAndGroupsManager.createPersonSearchQuery();
           query.text(id);
           query.limit(1);
           query.getMore().then(function (items) {
               //ensure results came back
               if (items.length > 0)
               {
                   //assume the first match is the user
                   var person = items[0].result;
                   person.status.get().then(function (s) {
                       deferred.resolve(s);
                   });

                   //check if we have already subscribed to this user
                   var subMatch = null;
                   for (var i = 0; i < userSubs.length; i++) {
                       if (userSubs[i].id === id) {
                           subMatch = userSubs[i];
                           break;
                       }
                   }
                   if (!subMatch) {
                       //no subscription exists for this user, so create one
                       userSubs.push({ id: id, person: person });

                       //listen for status changes
                       person.status.changed(function(s) {
                           //broadcast the status change to listeners
                           $rootScope.$broadcast("statusChanged", { user: person, status: s });
                       });

                       //subscribe to the status changes
                       person.status.subscribe();
                   }
               }
               else
                   deferred.reject("No matches found");
           });

           return deferred.promise;
       };
  1. Open the controllers.js file in the app folder and locate the ticketsCtrl. Update it dependency inject the skypeSvc you updated in the previous steps.
       .controller("ticketsCtrl", ["$scope", "helpdeskSvc", "skypeSvc", function($scope, helpdeskSvc, skypeSvc) {
           //get the helpdesk tickets
           helpdeskSvc.getTickets().then(function(tickets) {
               $scope.tickets = tickets;
               
               //add o365-skypeSubscribeTickets snippet here
           });

           //get the user's profile picture
           $scope.pic = "/content/nopic.jpg";
           helpdeskSvc.getProfilePic().then(function(img) {
               $scope.pic = img;
           });
           
           //add o365-skypeListenStatus snippet here

           //add o365-skypeCanChat snippet here

           //add o365-skypeStartChat snippet here

           //add $scope.closeChatWindow code here
       }]);
  1. Next, locate where getTickets returns tickets in the "then" promise. After setting $scope.tickets, you should ensure the user is signed into Skype for Business (using skypeSvc.ensureSignIn) and then loop through each ticket, subscribing to the ticket opener's status in Skype for Business (using skypeSvc.subscribeToStatus). You can use the o365-skypeSubscribeTickets snippet for this where it is indicated in comments of the starter file.
       //get the helpdesk tickets
       helpdeskSvc.getTickets().then(function(tickets) {
           $scope.tickets = tickets;

           //ensure the user is signed into skype
           skypeSvc.ensureSignIn().then(function() {
               angular.forEach($scope.tickets, function(ticket, index) {
                   //look up the status of the user and listen for changes
                   skypeSvc.subscribeToStatus(ticket.created_by.email).then(function(status) {
                       ticket.created_by.status = status;
                       if (!$scope.$$phase)
                           $scope.$apply(); //this provides async ui update out of thread
                   });
               });
           });
       });
  1. You also want to "listen" for status changes by Skype users. You already subscribed to these events with the Skype Web SDK in step 9, but you need to listen to the statusChanged event broadcast from the skypeSvc. You can do that as follows or using the o365-skypeListenStatus code snippet.

(Code Snippet - o365-skypeListenStatus)

       //listen for status changes
      $scope.$on("statusChanged", function(evt, data) {
          var id = data.user.id().replace("sip:", "").toLowerCase();

          //find all instances of this user
          angular.forEach($scope.tickets, function(ticket, index) {
              if (ticket.created_by.email.toLowerCase() === id) {
                  ticket.created_by.status = data.status;
                  if (!$scope.$$phase)
                      $scope.$apply();
              }
          });
      });
  1. Finally, you need to modify the view-tickets.html file located in the app > templates folder to display the Skype presence of each user. Locate the the repeated table row and update the first table cell as follows or using the o365-skypePresence code snippet.

(Code Snippet - o365-skypePresence)

       <tbody>
           <tr ng-repeat="ticket in tickets">
               <td>
                   <span class="badge" ng-class="ticket.created_by.status"><span class="glyphicon glyphicon-minus"></span></span>
                   <span>{{ticket.created_by.name}}</span>
               </td>
               <td>{{ticket.title}}</td>
               <td>{{ticket.status}}</td>
           </tr>
       </tbody>
  1. Launch the Skype for Business client by opening the Windows start menu and typing Skype for Business 2016. Once it launches, sign-in with the user account you have designated as the Skype User.

  2. Open a browser and navigate to http://localhost:8000. After you sign-in with the account you designated as the Browser User, you should see a presence icon next to each ticket opener. It might take a few seconds, but the TEST ACCOUNT should light up with the correct presence from Skype for Business.

  3. Try changing the presence of the Skype User in the Skype for Business 2016 client. After a few seconds, the new presence should display in the helpdesk app.

    Presence 1

    Presence 1

  4. You have successfully integrated Skype for Business presence into an existing web application. In the next task you will integrate instant messaging, which the Skype Web SDK makes really easy with a conversation UI.

    Presence 2

    Presence 2

Task 3 - Integrating Instant Messaging

In this task, you will continue to customize the Help Desk application to include instant messaging with ticket openers. The Skype for Business Web SDK include power controls that can help deliver a consistent Skype experience in your web applications.

  1. Open the skype.js file created in the previous task and update it with a new startConversation function on the skypeSvc object that accepts a SIP address for a user an initiates a conversation with them using the Skype Web SDK. All you have to do to initiate the Skype conversation UI is to use the apiManager and the renderConversation function, providing a DIV control in the page that it can render in ("chatWindowInner" in the example below) and the conversation details (participants, modalities, etc). You can follow the code below or use the o365-skypeStartConversation code snippet.

(Code Snippet - o365-skypeStartConversation)

       //start a conversation with a user
       skypeSvc.startConversation = function(sip) {
           //hide all containers
           var containers = document.getElementById("chatWindowInner").children;
           for (var i = 0; i < containers.length; i++) {
               containers[i].style.display = "none";
           }

           var chatSip = sip;
           var uris = [chatSip];
           var container = document.getElementById(chatSip);
           if (!container) {
               //this is a new conversation...create the window
               container = document.createElement("div");
               container.id = chatSip;
               document.getElementById("chatWindowInner").appendChild(container);
               var promise = apiManager.renderConversation(container, { modalities: ["Chat"], participants: uris });
           }
           else
               container.style.display = "block";
       };
  1. Next, you need to update the view-tickets.html file in the app > templates folder to accommodate the conversation UI. Add the following HTML to the bottom of this file or use the o365-skypeConversationUI code snippet.

(Code Snippet - o365-skypeConversationUI)

       <div id="chatWindow" ng-class="{'show': showChatWindow}">
           <span class="glyphicon glyphicon-remove close-chat" ng-click="closeChatWindow()"></span>
           <div id="chatWindowInner"></div>
       </div>
  1. While you are in the view-tickets.html file, you should also update the presence indicator to have a click event to start a conversation. In AngularJS, click events are configured using ng-click attribute. Set ng-click to call startChat(ticket).
   	<span class="badge" ng-class="ticket.created_by.status" ng-click="startChat(ticket)"><span class="glyphicon glyphicon-minus"></span></span>
  1. Next, open the controllers.js file in the app folder and locate the ticketsCtrl. At the bottom of this controller add a private canChat function that returns true/false based on the status and it's ability to accept instant messages. You can also add this using the o365-skypeCanChat code snippet.

(Code Snippet - o365-skypeCanChat)

       //helper function to check if a status can perform chat
       var canChat = function(status) {
           var chattableStatus = {
               Online: true, Busy: true, Idle: true, IdleOnline: true, Away: true, BeRightBack: true,
               DoNotDisturb: false, Offline: false, Unknown: false, Hidden: false };
           return chattableStatus[status];
       };
  1. Next, define a startChat function on the $scope object to initiate a conversation with the ticket opener. The function should accept a ticket parameter and check if the ticket opener is available to chat based on their availability (via the canChat function you just created). If the ticket opener is available for chat, you should call the skypeSvc.startConversation function from step 1 of this task. You can use the o365-skypeStartChat code snippet or follow the code below.

(Code Snippet - o365skypeStartChat)

       //starts a chat
       $scope.startChat = function(ticket) {
           if (canChat(ticket.created_by.status)) {
               skypeSvc.startConversation("sip:" + ticket.created_by.email);
               $scope.showChatWindow = true;
           }
       };
  1. Finally, add a closeChatWindow function on the $scope object to close the chat window. The $scope.showChatWindow property will dictate if chat window will be displayed. It is used in-conjunction with the ng-show attribute on the chat window markup.
       //closes the chat window
       $scope.closeChatWindow = function() {
           $scope.showChatWindow = false;
       };
  1. Open a browser and navigate to http://localhost:8000. After you sign-in with the account you designated as the Browser User, you should see a presence icon next to each ticket opener. It might take a few seconds, but the TEST ACCOUNT should light up with the correct presence from Skype for Business. Click on the presence icon to initiate a conversation with that user. The Skype conversation UI should fly in from the right and allow you to have a instant messaging conversation with the user.

    Integrated IM

    Integrated IM

  2. By successfully integrated presence and IM into the Help Desk application you have completed this exercise. Know that there are other Skype modalities in preview that you can develop against, including audio and video. Visit the Skype Quick Starts for more information.

## Summary ##

By completing this module, you should have:

  • Manually registered an Office 365 Connector and used a webhook to send data into Office 365.
  • Connected an existing web application to Office 365 via Office 365 Connectors, including the addition of "Connect to Office 365" buttons, handling callbacks from Office 365, and sending data into Office 365 via webhooks.
  • Skype-enabled and existing web application using the Skype Web SDK.

Note: You can take advantage of the Visual Studio Dev Essentials subscription in order to get everything you need to build and deploy your app on any platform.

You can’t perform that action at this time.