团队博客
使用 XCC-SDK 搭建简单话务条
前言
XCC 是 XSwitch Call Control(XSwitch 呼叫控制)的缩写。
XSwitch 是一个电信级的 IP 电话软交换系统和综合实时音视频多媒体通信平台,本文主要面向的是使用 XCC 的前端开发人。
XCC_SDK
SDK 原则上使用 HTTP
和 API
网关通信,可以扩展使用 WebSocket
, gRPC
等传输方式。
API 接口同时支持 HTTP
和 WebSocket
两种方式。
业务类接口采用的是 HTTP
方式,消息订阅采用的是 WebSocket
方式。请求及返回数据都为 JSON
格式。
SDK 引入
SDK 支持 es5
和 es6
语法和开发方式,具体可参考 SDK
使用代码示例 。
es5
方式可以直接在 HTML 文件中引入 JS,如:
<script type="text/javascript" src="static/js/index.min.js"></script>
在 es6
中,可以使用以下方法引入:
import { sys, Xcall } from "@xcall/js";
SDK 使用
本文采用的是 es5
的方式编写 demo, 但以下示例采用 es6
的方式:
import React, { useEffect } from "react"; import { sys, Xcall } from "@xcall/js"; // 初始session security为true 使用https/wss访问服务,全局唯一。 let session = null; // 默认使用http通信, stream 为 true 时, 表示使用websocket通信 const stream: boolean = false; let uc = null; const App: FC = () => { // 订阅事件 const subscribe = () => { session.subscribe({ channels: ["event.agent.xx", "event.channel.xx"], handler: (event: any) => { console.log(event); }, }); }; // 取消订阅 const unSubscribe = () => { session.unsubscribe({ channels: ["event.agent.xx", "event.agent.xx"], }); }; useEffect(() => { session = new Xcall({ host: "127.0.0.1:9090", security: false, contentType: "application/json", }); // 创建一个请求实例 uc = session.create(sys.ucenter, "sys.ucenter", stream); return () => { unSubscribe(); }; }, []); // 刷新token const saveToken = (token: string): void => { if (!token) return; session.setToken(token).then((response) => { console.log(message); if (response.code === 200) { // TODO } }); }; // 登陆 const handleLogin = (): void => { // 具体参数以 sys.ILoginRequest 为准 const params: sys.ILoginRequest = { username: "John", password: "1234", type: "account", dom: "xxx.com", }; uc.login(params) .then((response: sys.LoginResponse): void => { const { account, code } = response; if (code !== 200) { console.log("Logon failed", response.message); return; } saveToken(account?.token); }) .catch((reson: any) => { console.log(reson); }); }; return ( <div className="App"> <Button type="primary" onClick={handleLogin}> 登陆 </Button> </div> ); }; export default App;
实操
上述是如何引入 SDK 和使用 SDK,下面我们开始实操,搭建一个简易的话务条。
登陆系统获取基本信息
登陆系统后,我们需要获取到当前登陆用户的基本信息,包括用户 userid
,坐席分机号码 extensionNumber
等信息,以便我们后期使用。 使用示例如下:
<!DOCTYPE html> <html> <head> <title>使用XCC-SDK搭建简单话务条demo</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> <style> .btn-outline { display: flex; align-items: center; justify-content: center; border: 1px solid #f4f4f4; border-radius: 4px; color: limegreen; } </style> <script type="text/javascript" src="dist/index.min.js"></script> <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.3/css/bootstrap.css" /> </head> <body class="bg-light"> <div class="container align-middle"> <div class="row py-3 justify-content-center"> <div class="col-12 col-md-4" id="loginCard"> <div class="card"> <div class="card-body"> <h5>Connect</h5> <div class="form-group"> <label for="dom">域</label> <input type="text" class="form-control" id="dom" placeholder="Enter Dom" onchange="saveInLocalStorage(event)" /> </div> <div class="form-group"> <label for="app">账户</label> <input type="text" class="form-control" id="username" placeholder="Enter Username" onchange="saveInLocalStorage(event)" /> </div> <div class="form-group"> <label for="password">密码</label> <input type="text" class="form-control" id="password" placeholder="Enter your Password" onchange="saveInLocalStorage(event)" /> </div> <button id="btnConnect" class="btn btn-block btn-success" onclick="connect()" > 登陆 </button> <button id="btnDisconnect" class="btn btn-block btn-danger d-none" onclick="disconnect()" > 退出登录 </button> </div> </div> </div> </div> </div> <script type="text/javascript"> let client; var currentCall = null; let token = null; let stream = false; let host = "dev.xswitch.cn:9090"; // ucenter let ucenter; let currentUser = {}; let outDialNumber = ""; let extensionNumber = ""; // xcc let XCC; let agents; let username = localStorage.getItem("relay.example.username") || ""; let password = localStorage.getItem("relay.example.password") || ""; let dom = localStorage.getItem("relay.example.dom") || ""; // 关闭consloe.log logger.setLevel(5); /** * On document ready auto-fill the input values from the localStorage. */ window.onload = function () { document.getElementById("dom").value = dom; document.getElementById("username").value = username; document.getElementById("password").value = password; }; function saveInLocalStorage(e) { const key = e.target.id; window.localStorage.setItem(`relay.example.${key}`, e.target.value); } // 获取坐席信息 function getAgents(uuid) { agents .get({ uuid, }) .then(function (response) { if (response.code != 200) { console.error(response.message); return; } currentAgent = response.agent || {}; const state = getLabels(agentState, currentAgent.state); Status.innerText = state; // 获取坐席分机号码 extensionNumber = currentAgent.agentCidNumber || (currentUser.extensions.length ? currentUser.extensions[0] : ""); }); } /** * Connect with Relay creating a client and attaching all the event handler. */ function connect() { // 初始client security为true 使用https/wss访问服务,全局唯一。 client = new Xcall({ host, security: false, contentType: "application/json", }); // 创建ucenter请求实例 ucenter = client.create(sys.ucenter, "sys.ucenter", stream); // 创建XCC请求实例 XCC = client.create(xcc.Xcc, "xcc.Xcc", stream); // 坐席 agents = client.create(xcc.agent, "xcc.agent", stream); const params = { username: localStorage.getItem("relay.example.username"), password: localStorage.getItem("relay.example.password"), type: "account", dom: localStorage.getItem("relay.example.dom"), }; ucenter .login(params) .then(function (response) { console.log("userLogin", response); if (response.code != 200) { console.error(response.message); alert(response.message); return; } token = response.account.token; id = response.account.id; loginCard.classList.add("d-none"); traffic.classList.remove("d-none"); // 刷新token client.setToken(token); console.log("connent to ", client); queryCurrent(); btnConnect.classList.add("d-none"); btnDisconnect.classList.remove("d-none"); }) .catch(function (value) { console.log(value); }); // Update UI on socket close client.on("xcall.error", function () { btnConnect.classList.remove("d-none"); btnDisconnect.classList.add("d-none"); window.token = null; token = null; console.log("socket.close", token); }); } // 获取用户信息 function queryCurrent() { ucenter.currentUser({}).then(function (response) { if (response.code != 200) { console.error(response.message); return; } currentUser = response.currentUser; if (currentUser.isAgent) { getAgents(currentUser.userid); } }); } // 断开 function disconnect() { XCC = null; ucenter = null; btnConnect.classList.remove("d-none"); btnDisconnect.classList.add("d-none"); window.token = null; token = null; } </script> </body> </html>
subscribe 订阅事件/unsubscribe 取消订阅事件
用户登录之后,我们可以订阅事件用于监听 agent(坐席), channel(通话), queue(队列), message(消息) 等变化。
每个订阅事件都有一个唯一的 topic
,channels
数组里面的就是需要订阅服务的 topic
, 每个订阅事件订阅成功且有数据变化的时候,我们可以在 handler
这个函数里面获取到每个订阅事件推送等消息。
下面是示例代码
... // currentUser 就是当前登陆用户信息 const topicForAgent = `event.agent.${currentUser.userid}`; const topicForChannel = `event.channel.${currentUser.userid}`; const topicForAgentStatus = `event.agent.status.${currentUser.userid}` const sessionChannels = [topicForAgent, topicForChannel, topicForAgentStatus]; // 订阅事件 function subscribe() { client.subscribe({ channels: sessionChannels, handler: (e) => { const data = e.body; switch (e.topic) { case topicForAgentStatus: // 监听坐席状态变化 // TODO break; case topicForAgent: // TODO break; case topicForChannel: // TODO break; default: break; } }, }); } // 取消订阅 function unSubscribe() { if (sessionChannels.length && client) { client.unsubscribe({ channels: sessionChannels }); } }
用户退出系统时需要调用 unSubscribe
方法, 以便取消订阅,节省资源。
- 话务条基本功能 话务条基本功能包括签入
签出
呼叫
挂机
静音
转接
等等,我们选用以下几种常见等操作。
先来一段 html 编写下基本功能的页面,样式可以忽略!
<div class="col-12 col-md-8 mt-2 mt-md-1 d-none" id="traffic"> <div class="row"> <div id="Status" class="px-3 ml-2 col-md-auto btn-outline"></div> <button id="Login" class="btn px-3 ml-2 col-md-auto btn-success" onClick="onXcc(this)" > 签入 </button> <button id="Logout" class="btn btn-primary px-3 ml-2 col-md-auto btn-danger d-none" onClick="onXcc(this)" > 签出 </button> <input type="tel" class="form-control px-3 ml-2 col-md-2" id="DestNumber" disabled onchange="handleNumberChange(event)" /> <button id="Dial" class="btn btn-primary px-3 ml-2 col-md-auto" onClick="onXcc(this)" disabled > 呼叫 </button> <button id="Kill" class="btn btn-primary px-3 ml-2 col-md-auto btn-danger d-none" onClick="onXcc(this)" > 挂机 </button> <button id="Mute" class="btn btn-primary px-3 ml-2 col-md-auto" onClick="onXcc(this)" disabled > 静音 </button> <button id="unMute" class="btn btn-primary px-3 ml-2 col-md-auto d-none" onClick="onXcc(this)" > 解除静音 </button> <button id="Transfer" class="btn btn-primary px-3 ml-2 col-md-auto" onClick="onXcc(this)" disabled > 转接 </button> </div> </div>
空闲
坐席话务状态。签入
坐席签入按钮(点击该按钮,会变成签出
)。输入框
填写需要呼叫的号码。呼叫
呼叫按钮(点击该按钮,会变成挂机
)。静音
静音按钮(点击该按钮,会变成解除静音
)。转接
通话转接按钮。
坐席签入
外呼
通话中
坐席未 签入
前,输入框
呼叫
静音
, 转接
等操作不能生效。签入
后 输入框
,呼叫
处于可操作状态,其他按钮需要在通话中才可以操作状态,在实际开发中需要注意。
基本操作功能具体逻辑,代码如下:
... let outDialNumber = '' let extensionNumber = '' // xcc let XCC; let agents; // xcc callcenter let currentCallUUID = ''; let currentNodeUUID = ''; let currentPeerUUID = ''; let currentSessionUuid = '' // 是否来电弹屏 let isLayer = false; // 关闭consloe.log logger.setLevel(5); const agentState = [ { label: '空闲', value: xcc.AgentState.AgentStateIdle, }, { label: '空闲', // '就绪', value: xcc.AgentState.AgentStateWaiting, }, { label: '接收中', value: xcc.AgentState.AgentStateReceiving, }, { label: '队列通话中', value: xcc.AgentState.AgentStateInAQueueCall, }, { label: '保留', value: xcc.AgentState.AgentStateReserved, }, { label: '话后处理', value: xcc.AgentState.AgentStateAcw, }, { label: '呼叫中', value: xcc.AgentState.AgentStateOutboundCall, }, { label: '通话中', value: xcc.AgentState.AgentStateConnected, }, { label: '未知', value: xcc.AgentState.AgentStateUnknown, }, ]; /** * xcc call center. */ function onXcc(element) { console.log('call:' + element.id) if (token == null) { console.error("Failed to connect to server") return } switch (element.id) { case 'Login': XCC.login({ extensionNumber: extensionNumber, stationtype: xcc.StationType.SIP }) .then(function(res) { console.log(res, 'Login') if (res.code == 200) { Login.classList.add('d-none'); Logout.classList.remove('d-none'); Dial.removeAttribute('disabled'); DestNumber.removeAttribute('disabled'); } }) break case 'Logout': XCC.logout({}).then(function(res) { console.log(res, 'Login') Logout.classList.add('d-none'); Login.classList.remove('d-none'); Dial.disabled = true; DestNumber.disabled = true; DestNumber.value = ''; }) break case 'Dial': XCC.dial({ extensionNumber: extensionNumber, destNumber: outDialNumber }) .then(function(res) { console.log(res, 'Dial') if (res.code == 200) { currentCallUUID = res.callUuid currentNodeUUID = res.nodeUuid currentPeerUUID = res.peerUuid currentSessionUuid = res.sessionUuid; Kill.classList.remove('d-none'); Dial.classList.add('d-none'); return } alert(res.message) }) .catch(function(err) { console.log(err) }) break case 'Kill': XCC.hangup({ nodeUuid: currentCallUUID, callUuid: currentNodeUUID }).then(() => { if (res.code == 200) { Kill.classList.add('d-none'); Dial.classList.remove('d-none'); DestNumber.disabled = true; } }) break case 'unMute': XCC.setMute2({ nodeUuid: currentNodeUUID, callUuid: currentCallUUID, level: 0, direction: 'READ', flag: 'FIRST' }).then(function (res) { if (res.code != 200) { console.error(res.message) return } unMute.classList.add('d-none'); Mute.classList.remove('d-none'); console.log('解除坐席静音') }) break; case 'Mute': XCC.setMute2({ nodeUuid: currentNodeUUID, callUuid: currentCallUUID, level: 1, direction: 'READ', flag: 'FIRST' }).then(function (res) { if (res.code != 200) { console.error(res.message) return } Mute.classList.add('d-none'); unMute.classList.remove('d-none'); console.log('坐席静音') }) break; case 'Transfer': const callUuid = currentPeerUUID || (callingInfo.params?.cc_side == 'agent' ? callingInfo.params ?.cc_member_session_uuid : callingInfo.uuid); const nodeUuid = currentNodeUUID || callingInfo.node_uuid; let originCidNumber = outDialNumber; // 呼出 if (callingInfo.params?.xcc_is_call_out == 'true') { outDialNumber = callingInfo.params.xcc_origin_dest_number; } originCidNumber = outDialNumber || callingInfo.params.xcc_origin_cid_number; const calleeUuid = currentCallUUID || (callingInfo.params?.cc_side == 'agent' ? callingInfo.uuid : callingInfo.params?.cc_member_session_uuid); XCC.transferNew({ nodeUuid: currentCallUUID, callUuid: currentNodeUUID, extensionNumber, calleeUuid, destNumber, nodeUuid, callUuid, stationtype: xcc.StationType.PSTN, destAgentUid, originCidNumber, ccQueue: callingInfo.params?.cc_queue, ccMemberUuid: callingInfo.params?.cc_member_uuid }).then(() => { if (res.code == 200) { Kill.classList.add('d-none'); Dial.classList.remove('d-none'); } }) break default: console.log('default break on' + element.id) break } } // 获取呼叫号码 function handleNumberChange(e) { outDialNumber = e.target.value } // 获取label function getLabels(arrys, val, text) { if (val === undefined) return ''; const obj = arrys.find((item) => `${item.value}` === `${val}`); if (text) { return obj ? obj.text : ''; } return obj ? obj.label : ''; };
在话务条中,通话状态变化,坐席状态变化等都需要通过订阅事件来完成,我们改造下订阅事件:
... // 订阅事件 function subscribe() { client.subscribe({ channels: sessionChannels, handler: (e) => { const data = e.body; switch (e.topic) { case topicForAgentStatus: // TODO const state = getLabels(agentState, currentAgent.state); Status.innerText = state; break; case topicForAgent: // TODO break; case topicForChannel: // 来电弹屏 if (data.state == "CALLING") { console.log('channel msg:', JSON.stringify(data, null, 2)); } // 当前坐席端 if (data.params.xcc_uid == currentUser.userid) { handleChannel(data) } break; default: break; } }, }); } // channel消息处理函数 function handleChannel(data = {}) { // 呼入弹屏 if (data.state == 'CALLING' && data.params?.xcc_is_call_out != "true") { if (!isLayer && !data.answered) { // data.params?.xcc_origin_cid_number console.log('来电号码:' + data.params?.xcc_origin_cid_number) isLayer = true; // 这里实现具体业务逻辑 alert('来电号码:' + data.params?.xcc_origin_cid_number); } } // 是否通话中 isCalling = data.uuid && data.state != 'DESTROY'; if (isCalling) { callingInfo = data; // 可以设置转接/静音 Transfer.removeAttribute('disabled'); Mute.removeAttribute('disabled'); // 通话中不能签出操作 Logout.disabled = true; // 通话中不能输入 DestNumber.disabled = true; } else { // 恢复话务条初始状态 resetTraffic(); } } // 恢复话务条初始状态 function resetTraffic() { callingInfo = {}; tempChannels = []; isLayer = false; currentCallUUID = ''; currentNodeUUID = ''; currentPeerUUID = ''; currentSessionUuid = ''; DestNumber.value = ''; outDialNumber = ''; }
至于 转接
强拆
等功能和代码展示等 静音
功能类似,在实际开发中需要开发者按找 XCC-SDK 提供的API
传入对应的且正确的参数,就可以实现具体的功能,至于业务逻辑开发者可按照自己的需求进一步扩展。
topicForChannel
这个 topic
我们可以处理 来话
时的具体业务逻辑。
划重点
每次通话结束后需要把上路通话的相关缓存数据还原,具体可以参考 handleChannel
函数。
完整 demo
以下是简单的 demo code,样式相对简单
<!DOCTYPE html> <html> <head> <title>使用XCC-SDK搭建简单话务条demo</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <style> .btn-outline { display: flex; align-items: center; justify-content: center; border: 1px solid #f4f4f4; border-radius: 4px; color: limegreen; } </style> <script type="text/javascript" src="dist/index.min.js"></script> <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.3/css/bootstrap.css" /> </head> <body class="bg-light"> <div class="container align-middle"> <div class="row py-3 justify-content-center"> <div class="col-12 col-md-4" id="loginCard"> <div class="card"> <div class="card-body"> <h5>Connect</h5> <div class="form-group"> <label for="dom">域</label> <input type="text" class="form-control" id="dom" placeholder="Enter Dom" onchange="saveInLocalStorage(event)"> </div> <div class="form-group"> <label for="app">账户</label> <input type="text" class="form-control" id="username" placeholder="Enter Username" onchange="saveInLocalStorage(event)"> </div> <div class="form-group"> <label for="password">密码</label> <input type="text" class="form-control" id="password" placeholder="Enter your Password" onchange="saveInLocalStorage(event)"> </div> <button id="btnConnect" class="btn btn-block btn-success" onclick="connect()">登陆</button> <button id="btnDisconnect" class="btn btn-block btn-danger d-none" onclick="disconnect()">退出登录</button> </div> </div> </div> <div class="col-12 col-md-8 mt-2 mt-md-1 d-none" id="traffic"> <div class="row"> <div id="Status" class="px-3 ml-2 col-md-auto btn-outline"></div> <button id="Login" class="btn px-3 ml-2 col-md-auto btn-success" onClick="onXcc(this)">签入</button> <button id="Logout" class="btn btn-primary px-3 ml-2 col-md-auto btn-danger d-none" onClick="onXcc(this)">签出</button> <input type="tel" class="form-control px-3 ml-2 col-md-2" id="DestNumber" disabled onchange="handleNumberChange(event)"> <button id="Dial" class="btn btn-primary px-3 ml-2 col-md-auto" onClick="onXcc(this)" disabled>呼叫</button> <button id="Kill" class="btn btn-primary px-3 ml-2 col-md-auto btn-danger d-none" onClick="onXcc(this)">挂机</button> <button id="Mute" class="btn btn-primary px-3 ml-2 col-md-auto" onClick="onXcc(this)" disabled>静音</button> <button id="unMute" class="btn btn-primary px-3 ml-2 col-md-auto d-none" onClick="onXcc(this)">解除静音</button> <button id="Transfer" class="btn btn-primary px-3 ml-2 col-md-auto" onClick="onXcc(this)" disabled>转接</button> </div> </div> </div> </div> <script type="text/javascript"> let client; var currentCall = null; let token = null; let stream = false; let host = "dev.xswitch.cn:9090"; // ucenter let ucenter; let currentUser = {} let sessionChannels = []; let topicForAgent = ''; let topicForChannel = ''; let topicForAgentStatus = ''; let outDialNumber = '' let extensionNumber = '' // xcc let XCC; let agents; let username = localStorage.getItem('relay.example.username') || ''; let password = localStorage.getItem('relay.example.password') || ''; let dom = localStorage.getItem('relay.example.dom') || ''; // xcc callcenter let currentCallUUID = ''; let currentNodeUUID = ''; let currentPeerUUID = ''; let currentSessionUuid = '' // 是否来电弹屏 let isLayer = false; // 关闭consloe.log logger.setLevel(5); const agentState = [ { label: '空闲', value: xcc.AgentState.AgentStateIdle, }, { label: '空闲', // '就绪', value: xcc.AgentState.AgentStateWaiting, }, { label: '接收中', value: xcc.AgentState.AgentStateReceiving, }, { label: '队列通话中', value: xcc.AgentState.AgentStateInAQueueCall, }, { label: '保留', value: xcc.AgentState.AgentStateReserved, }, { label: '话后处理', value: xcc.AgentState.AgentStateAcw, }, { label: '呼叫中', value: xcc.AgentState.AgentStateOutboundCall, }, { label: '通话中', value: xcc.AgentState.AgentStateConnected, }, { label: '未知', value: xcc.AgentState.AgentStateUnknown, }, ]; /** * On document ready auto-fill the input values from the localStorage. */ window.onload = (function() { document.getElementById('dom').value = dom; document.getElementById('username').value = username; document.getElementById('password').value = password; }); function saveInLocalStorage(e) { const key = e.target.id; window.localStorage.setItem(`relay.example.${key}`, e.target.value) } // 获取坐席信息 function getAgents(uuid) { agents.get({ uuid }).then(function (response) { if (response.code != 200) { console.error(response.message); return; } currentAgent = response.agent || {}; const state = getLabels(agentState, currentAgent.state); Status.innerText = state; // 获取坐席分机号码 extensionNumber = currentAgent.agentCidNumber || (currentUser.extensions.length ? currentUser.extensions[0] : '') }); } /** * xcc call center. */ function onXcc(element) { console.log('call:' + element.id) if (token == null) { console.error("Failed to connect to server") return } switch (element.id) { case 'Login': XCC.login({ extensionNumber: extensionNumber, stationtype: xcc.StationType.SIP }) .then(function(res) { console.log(res, 'Login') if (res.code == 200) { Login.classList.add('d-none'); Logout.classList.remove('d-none'); Dial.removeAttribute('disabled'); DestNumber.removeAttribute('disabled'); } }) break case 'Logout': XCC.logout({}).then(function(res) { console.log(res, 'Login') Logout.classList.add('d-none'); Login.classList.remove('d-none'); Dial.disabled = true; DestNumber.disabled = true; DestNumber.value = ''; }) break case 'Dial': XCC.dial({ extensionNumber: extensionNumber, destNumber: outDialNumber }) .then(function(res) { console.log(res, 'Dial') if (res.code == 200) { currentCallUUID = res.callUuid currentNodeUUID = res.nodeUuid currentPeerUUID = res.peerUuid currentSessionUuid = res.sessionUuid; Kill.classList.remove('d-none'); Dial.classList.add('d-none'); return } alert(res.message) }) .catch(function(err) { console.log(err) }) break case 'Kill': XCC.hangup({ nodeUuid: currentCallUUID, callUuid: currentNodeUUID }).then(() => { if (res.code == 200) { Kill.classList.add('d-none'); Dial.classList.remove('d-none'); DestNumber.disabled = true; } }) break case 'unMute': XCC.setMute2({ nodeUuid: currentNodeUUID, callUuid: currentCallUUID, level: 0, direction: 'READ', flag: 'FIRST' }).then(function (res) { if (res.code != 200) { console.error(res.message) return } unMute.classList.add('d-none'); Mute.classList.remove('d-none'); console.log('解除坐席静音') }) break; case 'Mute': XCC.setMute2({ nodeUuid: currentNodeUUID, callUuid: currentCallUUID, level: 1, direction: 'READ', flag: 'FIRST' }).then(function (res) { if (res.code != 200) { console.error(res.message) return } Mute.classList.add('d-none'); unMute.classList.remove('d-none'); console.log('坐席静音') }) break; case 'Transfer': const callUuid = currentPeerUUID || (callingInfo.params?.cc_side == 'agent' ? callingInfo.params ?.cc_member_session_uuid : callingInfo.uuid); const nodeUuid = currentNodeUUID || callingInfo.node_uuid; let originCidNumber = outDialNumber; // 呼出 if (callingInfo.params?.xcc_is_call_out == 'true') { outDialNumber = callingInfo.params.xcc_origin_dest_number; } originCidNumber = outDialNumber || callingInfo.params.xcc_origin_cid_number; const calleeUuid = currentCallUUID || (callingInfo.params?.cc_side == 'agent' ? callingInfo.uuid : callingInfo.params?.cc_member_session_uuid); XCC.transferNew({ nodeUuid: currentCallUUID, callUuid: currentNodeUUID, extensionNumber, calleeUuid, destNumber, nodeUuid, callUuid, stationtype: xcc.StationType.PSTN, destAgentUid, originCidNumber, ccQueue: callingInfo.params?.cc_queue, ccMemberUuid: callingInfo.params?.cc_member_uuid }).then(() => { if (res.code == 200) { Kill.classList.add('d-none'); Dial.classList.remove('d-none'); } }) break default: console.log('default break on' + element.id) break } } // 获取呼叫号码 function handleNumberChange(e) { outDialNumber = e.target.value } /** * Connect with Relay creating a client and attaching all the event handler. */ function connect() { // 初始client security为true 使用https/wss访问服务,全局唯一。 client = new Xcall({ host, security: false, contentType: 'application/json', }); // 创建ucenter请求实例 ucenter = client.create(sys.ucenter, 'sys.ucenter', stream); // 创建XCC请求实例 XCC = client.create(xcc.Xcc, 'xcc.Xcc', stream); // 坐席 agents = client.create(xcc.agent, 'xcc.agent', stream); const params = { username: localStorage.getItem('relay.example.username'), password: localStorage.getItem('relay.example.password'), type: 'account', dom: localStorage.getItem('relay.example.dom') }; ucenter.login(params).then(function(response) { console.log("userLogin", response); if (response.code != 200) { console.error(response.message); alert(response.message) return; } token = response.account.token id = response.account.id; loginCard.classList.add('d-none'); traffic.classList.remove('d-none'); // 刷新token client.setToken(token); console.log('connent to ', client) queryCurrent(); btnConnect.classList.add('d-none'); btnDisconnect.classList.remove('d-none'); }).catch(function(value) { console.log(value); }); // Update UI on socket close client.on('xcall.error', function() { btnConnect.classList.remove('d-none'); btnDisconnect.classList.add('d-none'); window.token = null; token = null console.log('socket.close', token); }); } // 获取用户 function queryCurrent() { ucenter.currentUser({}).then(function (response) { if (response.code != 200) { console.error(response.message); return; } currentUser = response.currentUser; if (currentUser.isAgent) { topicForAgent = `event.agent.${currentUser.userid}`; topicForChannel = `event.channel.${currentUser.userid}`; topicForAgentStatus = `event.agent.status.${currentUser.userid}` sessionChannels = [topicForAgent, topicForChannel, topicForAgentStatus]; // 订阅 subscribe(); getAgents(currentUser.userid) } }); } // 断开 function disconnect() { XCC = null; ucenter = null; btnConnect.classList.remove('d-none'); btnDisconnect.classList.add('d-none'); window.token = null; token = null; // 取消订阅 unSubscribe(); } // 订阅事件 function subscribe() { client.subscribe({ channels: sessionChannels, handler: (e) => { const data = e.body; switch (e.topic) { case topicForAgentStatus: // TODO const state = getLabels(agentState, currentAgent.state); Status.innerText = state; break; case topicForAgent: // TODO break; case topicForChannel: // 来电弹屏 if (data.state == "CALLING") { console.log('channel msg:', JSON.stringify(data, null, 2)); } // 当前坐席端 if (data.params.xcc_uid == currentUser.userid) { handleChannel(data) } break; default: break; } }, }); } // 获取label function getLabels(arrys, val, text) { if (val === undefined) return ''; const obj = arrys.find((item) => `${item.value}` === `${val}`); if (text) { return obj ? obj.text : ''; } return obj ? obj.label : ''; }; // channel消息处理函数 function handleChannel(data = {}) { // 呼入弹屏 if (data.state == 'CALLING' && data.params?.xcc_is_call_out != "true") { if (!isLayer && !data.answered) { // data.params?.xcc_origin_cid_number console.log('来电号码:' + data.params?.xcc_origin_cid_number) isLayer = true; // 这里实现具体业务逻辑 alert('来电号码:' + data.params?.xcc_origin_cid_number); } } // 是否通话中 isCalling = data.uuid && data.state != 'DESTROY'; if (isCalling) { callingInfo = data; // 可以设置转接/静音 Transfer.removeAttribute('disabled'); Mute.removeAttribute('disabled'); // 通话中不能签出操作 Logout.disabled = true; // 通话中不能输入 DestNumber.disabled = true; } else { // 恢复话务条初始状态 resetTraffic(); } } // 恢复话务条初始状态 function resetTraffic() { callingInfo = {}; tempChannels = []; isLayer = false; currentCallUUID = ''; currentNodeUUID = ''; currentPeerUUID = ''; currentSessionUuid = ''; DestNumber.value = ''; outDialNumber = ''; } // 取消订阅 function unSubscribe() { if (sessionChannels.length && client) { client.unsubscribe({ channels: sessionChannels }); } } </script> </body> </html>
开发无止境,一切皆可能。