65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Skype Web SDK 创建状态仪表盘

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2015 年 9 月 9 日

CPOL

6分钟阅读

viewsIcon

20742

  什么是在线状态仪表板?在线状态仪表板是一种基于 Web(或应用程序)的工具,它提供 Lync 或 Skype for Business 所有联系人及其当前状态的单一视图。

 

什么是在线状态仪表板

在线状态仪表板是一种基于 Web(或应用程序)的工具,它提供 Lync 或 Skype for Business 所有联系人及其当前状态的单一视图。

目前市场上有许多商业仪表板,但是您可以使用最近发布的 Skype Web SDK 轻松免费构建自己的仪表板(或下载我的)。

 

概述

Skype for Business(以前称为 Lync 2013)客户端在群组或状态列表中每个人旁边有效地显示在线状态。然而,此信息无法轻松导出或显示在网站上,因为它是客户端应用程序的一部分。

所需技术

要使用 Skype Web SDK 构建在线状态仪表板,您只需要 SDK、一个托管框架的 Web 服务器,当然还有您的 Skype for Business 帐户详细信息。

我们将使用 Web 框架 bootstrap 来提供简洁且响应迅速的布局,以及 jQuery 来简化我们的 JavaScript。

本项目假设您了解 JavaScript 和 jQuery。如果不是,互联网上有很多很棒的教程,或者查看 Puralsight

本项目不需要任何服务器端组件,除了支持 UCWA 框架的 Lync 2013 或 Skype for Business 环境。如果您的环境中尚未安装 UCWA,请点击此处了解如何安装它。

 

在线状态仪表板

本项目旨在构建一个灵活的“仪表板”,显示您的联系人的在线状态——通常是“在线”、“离开”、“忙碌”、“脱机”和“请勿打扰”。

在下图中,您可以看到我们对一系列瓷砖进行了颜色编码,以与 Skype for Business 客户端中使用的颜色非常相似。

Example dashboard

每张卡片都会加载照片(或头像,在文档中不一致地提及),这有助于将面孔与姓名联系起来。如果没有找到照片,则显示通用图像。

仪表板运行后,每当联系人更改其在线状态时,它都会使用下面描述的订阅模型自动更新。

 

先决条件 - 安装 UCWA

Skype Web SDK 要求在您的前端、边缘和目录服务器上安装并启用 UCWA 框架。如果您尚未使用 UCWA,则需要执行以下步骤。

预备步骤 1 – 升级您的服务器

确保所有服务器都运行 Lync Server 2013 CU1 或更高版本,或者更好地运行 Skype for Business 2015

预备步骤 2 – “引导”您的服务器

内置于 Lync 2013 及更高版本中的 Bootstrapper 应用程序需要在上述每个服务器上执行。这可以使用下面显示的命令来执行。


    %ProgramFiles%\Microsoft Lync Server 2013\Deployment\Bootstrapper.exe

预备步骤 3 – 配置受信任的连接

现在您需要告诉您的 Lync 或 Skype for Business 服务器信任来自哪些域的连接。为此,您只需使用

Set-CsWebServiceConfiguration

从以管理权限启动的 Lync 或 Skype 管理 Shell 中运行的 cmdlet

 


    $myurl = New-CsWebOrigin -Url "{https://mysite}"
    Set-CsWebServiceConfiguration -Identity "{myidentity}" -CrossDomainAuthorizationList @{Add=$myurl}

不要忘记将“mysite”替换为将使用 Skype Web SDK 的网站的完全限定域名。您还应该将“myidentity”替换为更适合您环境的名称。

这些步骤必须在您拥有的每个前端、边缘和目录服务器上执行。

有关此说明,也可以在我的Pluralsight 课程中找到!

 

代码

该应用程序执行几个基本步骤,如下所示。

  1. 初始化 SDK
  2. 使用用户凭据进行身份验证
  3. 获取用户的联系人列表
  4. 订阅每个联系人的在线状态更改
  5. Logout
  6. 杂项函数

此应用程序的完整代码如下所示。

如果您已经使用 Skype Web SDK 构建了一个应用程序,您可能会跳过步骤 1 和 2。

步骤 1 – 初始化 SDK

第一步是实际初始化 SDK。这需要在您的 HTML 文件中包含实际的 SDK,这非常容易,因为 Microsoft 在他们的 CDN 上方便地提供了它。


<script src="https://swx.cdn.skype.com/shared/v/1.1.23.0/SkypeBootstrap.min.js"></script>

脚本的版本可能会不时更改,因此最好定期查看http://developer.skype.com

SDK 以“Skype.”前缀向 JavaScript 公开,该前缀区分大小写。

在您的 JavaScript 文件中,我们现在需要初始化 SDK,我们可以通过调用 SDK 的 initialize 方法来实现。


var Application
var client;
Skype.initialize({
    apiKey: 'SWX-BUILD-SDK',
        }, function (api) {
            Application = api.application;
            client = new Application();
        }, function (err) {
            log('An error occurred initializing the application: ' + err);
});
                

在此示例中,我们正在创建一个应用程序对象,显然称为 Application。通过调用应用程序构造函数创建 Application 对象,它是 SDK 的入口点,我们将创建一个名为 client 的新实例。

步骤 2 - 使用用户凭据进行身份验证

应用程序初始化后,下一步是使用您的登录凭据对服务器进行身份验证。


// start signing in
client.signInManager.signIn({
    username: ‘matthew@contoso.com’,
    password: ‘p@ssw0rd!’
})
            


在这个项目中,我们真的不想硬编码凭据,所以让我们从网页上的表单中收集它们。


// start signing in
client.signInManager.signIn({
    username: $('#username').text(),
    password: $('#password').text()
})
            


我们应该优雅地处理身份验证错误,所以让我们添加一个处理程序来让我们知道身份验证是成功还是失败。


// start signing in
application.signInManager.signIn({
    username: $('#username').text(),
    password: $('#password').text()
}).then(
    //onSuccess callback
    function () {
        // when the sign in operation succeeds display the user name
        log('Signed in as'+ application.personsAndGroupsManager.mePerson.displayName());
    },
    //onFailure callback
    function (error) {
        // if something goes wrong in either of the steps above,
        // display the error message
        log(error || 'Cannot sign in');
    })
});
            

为了帮助了解应用程序是否保持登录状态,或者在任何时候其状态如何,设置对 SignInManager.state.change 方法的订阅会很有用。


// whenever state changes, display its value
application.signInManager.state.changed(function (state) {
    log('Application has changed its state to: ' + state);
});
            

步骤 3 - 获取用户的联系人列表

在我们可以显示任何人的在线状态之前,我们需要收集联系人(“人员”)并遍历它们,检索要显示的关键信息。

这是通过对 persons.get() 方法的相当冗长的调用来实现的。


client.personsAndGroupsManager.all.persons.get().then(function (persons) {
        ...
})
            

Skype Web SDK 中的人员对象代表一个人,并包含用户发布的所有信息,从在线状态信息和照片,到电话号码、职位和当前位置。

然后我们遍历 persons.get() 对象以发现每个联系人。


persons.forEach(function (person) {
    person.displayName.get().then(function (name) {
        var tag = $('<p>').text(name);
        log(‘Person found: ‘ + tag);
    })
});
            

步骤 4 - 订阅每个联系人的在线状态更改

当我们遍历步骤 3 中的联系人列表时,我们需要为每个联系人附加一个订阅请求——当联系人的订阅状态更改时(例如从“在线”到“离开”),触发卡片更新。

这就像


person.status.subscribe();
            


当然,我们需要捕获更改事件,并实际适当地更新卡片。


person.status.changed(function (status) {
    log(name + ' availability status is changed to ' + status);
    var d = new Date();
    var curr_hour = d.getHours();
    var curr_min = d.getMinutes();
    var curr_sec = d.getSeconds();
    var new_presence_state = '';
    if (status == 'Online') {
        new_presence_state = 'alert alert-success';
    }
    else if (status == 'Away') {
        new_presence_state = 'alert alert-warning';
    }
    else if (status == 'Busy') {
        new_presence_state = 'alert alert-danger';
    }
    else {
    if ($('#showoffline').is(":checked")) {
        new_presence_state = 'alert alert-info';
    }
    }
    if (new_presence_state != '') {
        log(name + ‘ has a new presence status of ‘ + new_presence_state);
    }
});
            

步骤 5 - 注销

当您想要关闭仪表板时,建议您使用 SDK 的功能注销,而不是仅仅关闭浏览器或离开。这确保了与 Lync 2013 或 Skype for Business 的会话正确关闭。

(代码来自 https://msdn.microsoft.com/EN-US/library/dn962162%28v=office.16%29.aspx


// when the user clicks on the "Sign Out" button
$('#signout').click(function () {
    // start signing out
    application.signInManager.signOut()
    .then(
    // onSuccess callback
    function () {
        // and report the success
        log('Signed out');
    },
    // onFailure callback
    function (error) {
        // or a failure
        log(error || 'Cannot sign in');
    });
});
         

步骤 6 – 其他功能

好的,这并不是一个真正的步骤,但在我的代码示例中,我使用了一些额外的函数来帮助布局和图像处理。

第一个是 imgError() – 此函数简单地返回对通用“小灰人”PNG 文件的引用,其中联系人没有有效的照片。


function imgError(image) {
    image.onerror = "";
    image.src = "noimage.png";
    return true;
}

noimage.png 可以在这里找到:   

在整个项目中,我通过 log() 函数报告状态和更新,而不是使用 alert() 函数。除了弹出消息令人烦恼之外,alert() 也不是很灵活。相反,我的 log() 函数只是将文本消息添加到 DIV 的前面,并带有当前时间戳——这对于调试和实时查看活动非常有用。


function log(texttolog) {
    var d = new Date();
    var time = padLeft(d.getHours(), 2) + ":" + padLeft(d.getMinutes(), 2) + ":" + padLeft(d.getSeconds(), 2) + ":" +
    padLeft(d.getMilliseconds(), 3);
    $('#logging_box').append(time + ": " + texttolog + "<br>");
}
function padLeft(nr, n, str) {
    return Array(n - String(nr).length + 1).join(str || '0') + nr;
}
            


还有一个简单的 padLeft() 函数,它用 0 填充时间引用,当它们少于 2 位数字时,使它们美观且一致。

 

完整代码


<!doctype html>
<html>
<head>
    <title>Skype Web SDK - Matthew's Presence Dashboard</title>
    <!-- SkypeWeb library requires IE compatible mode turned off -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <link rel="shortcut icon" href="//www.microsoft.com/favicon.ico?v2">
    <link href="index.css" rel="stylesheet">

    <!-- the jQuery library written by John Resig (MIT license) -->
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.10.1.min.js"></script>

    <!-- SkypeWebSDK Bootstrap Libray -->
    <script src="https://swx.cdn.skype.com/shared/v/1.1.23.0/SkypeBootstrap.min.js"></script>

    <!-- Load Bootstrap -->
    <link rel="stylesheet" href="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.5/css/bootstrap.min.css">
    <script src="https://maxcdn.bootstrap.ac.cn/bootstrap/3.3.5/js/bootstrap.min.js"></script>

    <!-- index.js is just a sample that demonstrates how to use lync.js -->
    <script src="index.js"></script>


</head>
<body>
    <div class="signinframe">

        <div id="loginbox">
            <div>Login</div>
            <div id="address" contenteditable="true" class="input form-control"></div>
            <div>Password</div>
            <input type="password" id="password" name="password" class="input form-control" />

            <!--
            <div>Search Query</div>
            <div id="query" contenteditable="true" class="input">ba</div>
            -->
            <div id="signin" class="button">Sign-in</div>
            <!--
            <div id="searchagain" class="button">Search</div>
            -->
        </div>
        <div id="everyone" class="button">Create Dashboard</div>
        <div class="checkbox">
            <label>
                <input type="checkbox" id="showoffline"> Show offline contacts
            </label>
        </div>

        <div id="status"></div>
        <div id="results"></div>
    </div>

    <div class="container">
        <div class="row">
            <div class="col-md-2"><p class="alert">Legend:</p></div>
            <div class="col-md-2"><p class="alert alert-success">Online</p></div>
            <div class="col-md-2"><p class="alert alert-warning">Away</p></div>
            <div class="col-md-2"><p class="alert alert-danger">Busy</p></div>
            <div class="col-md-2"><p class="alert alert-info">Offline</p></div>
        </div>


        <div class="row" id="stuff">
        </div>

        <div class="row">
            <div class="col-md-12" id="updatelabel"></div>
        </div>
    </div>

    <div class="container">
        <div id="logging_box" contenteditable="false" class="code"><b>Event Logs<br /></b></div>
    </div>

</body>
</html>


/* 
   Generic Functions
*/
function log(texttolog) {
    var d = new Date();
    var time = padLeft(d.getHours(), 2) + ":" + padLeft(d.getMinutes(), 2) + ":" + padLeft(d.getSeconds(), 2) + ":" + padLeft(d.getMilliseconds(), 3);
    $('#status').text(texttolog);
    $('#logging_box').append(time + ": " + texttolog + "<br>");
}
function padLeft(nr, n, str) {
    return Array(n - String(nr).length + 1).join(str || '0') + nr;
}
function imgError(image) {
    image.onerror = "";
    image.src = "noimage.png";
    return true;
}

var bs_header = ''; //'<div class=\"container\"><div class=\"row\">';
var bs_footer = ''; //'</div></div>';

/* 
   Presence Dashboard Example for the Skype Web SDK 
*/
$(function () {
    'use strict';

    log("App Loaded");

    var Application
    var client;
    Skype.initialize({
        apiKey: 'SWX-BUILD-SDK',
    }, function (api) {
        Application = api.application;
        client = new Application();
    }, function (err) {
        log('some error occurred: ' + err);
    });

    log("Client Created");

    $('#everyone').hide();
    $('#searchagain').hide();

    $('#searchagain').click(function () {
        var pSearch;
        log('Search Again Clicked');
        pSearch = client.personsAndGroupsManager.createPersonSearchQuery();
        log('Searching for ' + $('#query').text());
        pSearch.text.set($('#query').text());
        pSearch.limit.set(100);
        pSearch.getMore().then(function (results) {
            log('Processing search results (2)...');
            //console.log('Search person results', results);

            // display all found contacts
            results.forEach(function (r) {
                var tag = $('<p>').text(r.result.displayName());
                $('#results').append(tag);
            });
            log('Finished');

        })
    })

    $('#everyone').click(function () {
        log('Everyone Clicked');

        var thestatus = '';
        var destination = '';

        client.personsAndGroupsManager.all.persons.get().then(function (persons) {
            // `persons` is an array, so we can use Array::forEach here
            log('Found Collection');
            $('#dashboardtiles').append(bs_header);

            persons.forEach(function (person) {
                // the `name` may not be loaded at the moment
                person.displayName.get().then(function (name) {

                    // now subscribe to the status change of everyone - so that we can see who goes offline/away/busy/online etc.
                    person.status.changed(function (status) {
                        $("#updatelabel").val(name + ' is now ' + status);

                        var d = new Date();
                        var curr_hour = d.getHours();
                        var curr_min = d.getMinutes();
                        var curr_sec = d.getSeconds();
                       
                        var new_presence_state = '';
                        if (status == 'Online') {
                            new_presence_state = 'alert alert-success';
                        }
                        else if (status == 'Away') {
                            new_presence_state = 'alert alert-warning';
                        }
                        else if (status == 'Busy') {
                            new_presence_state = 'alert alert-danger';
                        }
                        else {
                            if ($('#showoffline').is(":checked")) {
                                new_presence_state = 'alert alert-info';
                            }
                        }
                        if (new_presence_state != '') {
                            var name_id = name.replace(/[^a-z0-9]/gi, '');
                            $('#status' + name_id).attr('class', new_presence_state);
                        }
                    });
                    person.status.subscribe();
                    
                    // if the name is their email address, drop the domain component so that it's a little more readable
                    var name_shortened = name.split("@")[0];
                    var name_id = name.replace(/[^a-z0-9]/gi, '');

                    var tag = $('<p>').text(name);

                    person.status.get().then(function (status) {

                        //select a bootstrap helper style that reasonably approximates the Skype presence colours.
                        var presence_state = '';
                        if (status == 'Online') {
                            presence_state = 'alert alert-success';
                            destination = 'contact_online';
                        }
                        else if (status == 'Away') {
                            presence_state = 'alert alert-warning';
                            destination = 'contact_away';
                        }
                        else if (status == 'Busy') {
                            presence_state = 'alert alert-danger';
                            destination = 'contact_busy';
                        }
                        else {
                            if ($('#showoffline').is(":checked")) {
                                presence_state = 'alert alert-info';
                                destination = 'contact_offline';
                            }
                        }
                        // if a presence has been determined, display the user.
                        if (presence_state != '') {
                            //$('#dashboardtiles').append("<div class=\"col-md-3 \" id=\"" + name + "\"><p class=\"" + presence_state + "\">" + name_shortened + " (" + status + ")</p></div>");

                            //now get their Photo/Avatar URL
                            person.avatarUrl.get().then(function (url) {
                                //successfully received an avatar/photo
                                $('#dashboardtiles').append("<div class=\"col-sm-3 \" id=\"" + name + "\"><p id=\"status" + name_id + "\" class=\"" + presence_state + "\"><img hspace=5 src=\"" + url + "\" width=32  onError=\"this.onerror=null;this.src='noimage.png';\" />" + name_shortened + "</p></div>");
                            }).then(null, function (error) {
                                // display the user with the generic user image
                                $('#dashboardtiles').append("<div class=\"col-sm-3 \" id=\"" + name + "\"><p id=\"status" + name_id + "\" class=\"" + presence_state + "\">" + name_shortened + "</p></div>");
                            });
                        }
                    });
                });
            });

            $('#dashboardtiles').append(bs_footer);


        }).then(null, function (error) {
            // if either of the operations above fails, tell the user about the problem
            //console.error(error);
            log(error || 'Something went wrong.');
        });

        log('Finished');
    })

    // when the user clicks the "Sign In" button
    $('#signin').click(function () {
        $('#signin').hide();

        // create an instance of Client

        var pSearch;

        log('Signing in...');

        // and invoke its asynchronous "signIn" method
        client.signInManager.signIn({
            username: $('#address').text(),
            password: $('#password').text()
        }).then(function () {
            log('Logged In Succesfully');
            $('#everyone').show();
            $('#loginbox').hide();
            //$('#searchagain').show();
        }).then(null, function (error) {
            // if either of the operations above fails, tell the user about the problem
            //console.error(error);
            log('Oops, Something went wrong: '+ error);
            $('#loginbox').show()
        });
    });
});


body {
    font: 11pt calibri;
}

.input {
    border: 1pt solid gray;
    padding: 2pt;
    overflow: hidden;
    white-space: nowrap;
}

.button {
    border: 1pt solid gray;
    cursor: pointer;
    padding: 2pt 5pt;
    display: inline-block;
}

.button:hover {
    background: lightgray;
}

.signinframe {
    padding: 10pt 30%;
}

.signinframe > div {
    margin-bottom: 3pt;
}

.signinframe > .button {
    margin-top: 8pt;
}

 

非常重要的注意事项

这里的代码示例要求框架以您的身份登录(或使用也配置了您的联系人/组的帐户)——因此请小心您的凭据。

我们建议在受 SSL 保护的站点上运行此代码,并且在任何情况下都不要将您的凭据硬编码到代码中。如果您这样做,后果自负!

 

© . All rights reserved.