@94ai/softphone 5.0.10 → 5.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/html-softphone-demo/WebrtcDiver.js +320 -0
- package/html-softphone-demo/embed-ui/index.html +77 -0
- package/html-softphone-demo/embed-ui/softphone.js +465 -0
- package/html-softphone-demo/embed-ui/util.js +123 -0
- package/html-softphone-demo/index-channel.html +904 -0
- package/html-softphone-demo/index-local.html +3 -10
- package/html-softphone-demo/index-switch-pcm.html +613 -0
- package/html-softphone-demo/{index-other.html → index-test.html} +10 -3
- package/html-softphone-demo/index.html +46 -27
- package/html-softphone-demo/micro-call-ui/CryptoJS.js +3 -0
- package/html-softphone-demo/micro-call-ui/index.html +75 -0
- package/html-softphone-demo/micro-call-ui/microphone.js +829 -0
- package/html-softphone-demo/micro-call-ui/qiankun.js +2 -0
- package/html-softphone-demo/micro-call-ui/util.js +17 -0
- package/html-softphone-demo/micro-call-ui/wujie.js +3 -0
- package/html-softphone-demo/pcm-server/16bit-44100.pcm +0 -0
- package/html-softphone-demo/pcm-server/index.html +110 -0
- package/html-softphone-demo/pcm-server/server.js +54 -0
- package/html-softphone-demo/softphone.umd.min.js +1 -2
- package/html-softphone-demo.7z +0 -0
- package/lib/index.d.ts +1 -1
- package/lib/softphone.cjs.min.cjs +1 -1
- package/lib/softphone.esm-bundler.min.mjs +1 -1
- package/lib/softphone.umd.min.js +1 -2
- package/package.json +1 -1
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport"
|
|
6
|
+
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
|
|
7
|
+
<title>渠道软电话sdk功能示例</title>
|
|
8
|
+
<style>
|
|
9
|
+
#softphone-app {
|
|
10
|
+
padding-top: 50px;
|
|
11
|
+
overflow: hidden;
|
|
12
|
+
}
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<div id="softphone-app">
|
|
17
|
+
<div style="float: left;margin-left: 20px">
|
|
18
|
+
<div style="margin-bottom: 15px">
|
|
19
|
+
一、当前坐席信息:
|
|
20
|
+
</div>
|
|
21
|
+
<pre id="info"></pre>
|
|
22
|
+
<div>
|
|
23
|
+
2. 当前坐席交流传递到远端的音量大小:<span id="volumn">0.00</span>
|
|
24
|
+
<div>- (强度比例0.00~1.00,用以实现如下音量反馈效果)</div>
|
|
25
|
+
<img src="./tapd_35238004_base64_1695010473_859.gif" style="width: 460px" />
|
|
26
|
+
</div>
|
|
27
|
+
<div style="margin-bottom: 15px">
|
|
28
|
+
3. 当前浏览器状态(通过权限网络检测获取):
|
|
29
|
+
</div>
|
|
30
|
+
<pre id="browserSoftphoneEnv"></pre>
|
|
31
|
+
</div>
|
|
32
|
+
<div style="float: left;margin-left: 10px">
|
|
33
|
+
<div style="margin-bottom: 15px">
|
|
34
|
+
二、调试步骤:
|
|
35
|
+
</div>
|
|
36
|
+
<div>1. 获取坐席是否在线
|
|
37
|
+
<div>
|
|
38
|
+
- agentStatus === 1 -> 在线
|
|
39
|
+
</div>
|
|
40
|
+
<div>
|
|
41
|
+
- agentStatus === 3 -> 小休
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
- tip: 能外呼的条件是:
|
|
45
|
+
<div style="margin-left: 20px;">
|
|
46
|
+
<div>
|
|
47
|
+
1. 软电话签入
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
2. 坐席在线
|
|
51
|
+
</div>
|
|
52
|
+
<div>
|
|
53
|
+
3. 同时确保坐席所在企业配置 【坐席外呼依赖】配置项 是【标识】 (默认是【登录】)
|
|
54
|
+
</div>
|
|
55
|
+
<div style="margin-left: 20px;">
|
|
56
|
+
- 配置项 【登录】:只有登录了决策系统才可外呼。
|
|
57
|
+
</div>
|
|
58
|
+
<div style="margin-left: 30px;">
|
|
59
|
+
(tip: 如果不确定联系运维在管理后台查看以及配置)
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
</div>
|
|
65
|
+
<div>2. 设置坐席在线</div>
|
|
66
|
+
<div>3. 连接外呼服务器(软电话签入)</div>
|
|
67
|
+
<div>4. 手动呼叫或任务外呼</div>
|
|
68
|
+
<div>- 手动呼叫,外呼服务器会先呼叫坐席,坐席接听后才会正式呼叫用户</div>
|
|
69
|
+
<div>- 任务外呼,外呼服务器会先呼叫用户,用户接听后才会正式呼叫坐席,坐席接听前有ai和用户对话</div>
|
|
70
|
+
<div>5. 接听 </div>
|
|
71
|
+
<div>6. 转人工(根据任务的配置:是否是监听任务,手动外呼不是任务外呼,不用转人工) </div>
|
|
72
|
+
<div>- tip:监听任务:接听后只可以听到ai和用户的对话,转人工后才可以和用户直接对话 </div>
|
|
73
|
+
<div>7. 挂断</div>
|
|
74
|
+
</div>
|
|
75
|
+
<div style="float: left;margin-left: 20px">
|
|
76
|
+
<div style="margin-bottom: 15px">
|
|
77
|
+
三、具体步骤:
|
|
78
|
+
</div>
|
|
79
|
+
<button id="nap">设置当前坐席进入小休状态</button>
|
|
80
|
+
<br>
|
|
81
|
+
<br>
|
|
82
|
+
<button id="online">1. 设置当前坐席在线</button>
|
|
83
|
+
<br>
|
|
84
|
+
<br>
|
|
85
|
+
<button id="connectToServer">2. 连接外呼服务器</button>
|
|
86
|
+
<br>
|
|
87
|
+
<br>
|
|
88
|
+
<button id="disconnect" disabled>断开连接服务器</button>
|
|
89
|
+
<br>
|
|
90
|
+
<br>
|
|
91
|
+
<button id="callButton">3. 手动呼叫</button>
|
|
92
|
+
: <input id="call" />
|
|
93
|
+
<br>
|
|
94
|
+
<br>
|
|
95
|
+
<button id="ignore" disabled>忽略</button>
|
|
96
|
+
<br>
|
|
97
|
+
<br>
|
|
98
|
+
<button id="answer" disabled>4. 接听</button>
|
|
99
|
+
<br>
|
|
100
|
+
<br>
|
|
101
|
+
<button id="hangUp" disabled>5. 挂断</button>
|
|
102
|
+
<br>
|
|
103
|
+
<br>
|
|
104
|
+
<button id="sendStarDTMF" disabled>按*转人工</button>
|
|
105
|
+
<br>
|
|
106
|
+
<br>
|
|
107
|
+
<button id="localVoiceAccess" disabled>本地声音接入(默认接入)</button>
|
|
108
|
+
<br>
|
|
109
|
+
<br>
|
|
110
|
+
<button id="remoteSoundAccess" disabled>远程声音接入(默认接入)</button>
|
|
111
|
+
<br>
|
|
112
|
+
<br>
|
|
113
|
+
<button id="localSoundDisconnection" disabled>本地声音断开</button>
|
|
114
|
+
<br>
|
|
115
|
+
<br>
|
|
116
|
+
<button id="remoteSoundDisconnection" disabled>远程声音断开</button>
|
|
117
|
+
<br>
|
|
118
|
+
<br>
|
|
119
|
+
<button id="check">权限网络检测</button>
|
|
120
|
+
<br>
|
|
121
|
+
<br>
|
|
122
|
+
<audio
|
|
123
|
+
id="remoteAudio"
|
|
124
|
+
controls
|
|
125
|
+
style="display:none"
|
|
126
|
+
>
|
|
127
|
+
<p>Your browser doesn't support HTML5 audio - remoteAudio</p>
|
|
128
|
+
</audio>
|
|
129
|
+
<audio
|
|
130
|
+
id="localAudio"
|
|
131
|
+
style="display:none"
|
|
132
|
+
src="./antique_phone.mp3"
|
|
133
|
+
controls
|
|
134
|
+
loop
|
|
135
|
+
>
|
|
136
|
+
<p>Your browser doesn't support HTML5 audio - localAudio</p>
|
|
137
|
+
</audio>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
<div>
|
|
141
|
+
四、当前聊天记录信息:
|
|
142
|
+
<pre id="chatInfo">
|
|
143
|
+
</pre>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<script src="./fp.umd.min.js"></script>
|
|
147
|
+
<script src="./CryptoJS.js"></script>
|
|
148
|
+
<script src="./softphone.umd.min.js"></script>
|
|
149
|
+
|
|
150
|
+
<script>
|
|
151
|
+
// const pc = new RTCPeerConnection();
|
|
152
|
+
// pc.createOffer({offerToReceiveAudio: true}).then(offer => console.log(offer.sdp, 'RTCPeerConnection supports sdp'))
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 0.确保你的企业 【坐席外呼依赖】配置项 是【标识】,以开放外呼权限 (默认是【登录】,如果不确定联系运维在管理后台查看以及配置)
|
|
156
|
+
* 1. 使用 【https://tsgm49jebh.feishu.cn/docx/PPMtdY7EToP92qxexyNcvnoCn5f 《API开放文档(全)》 密码:api12345】
|
|
157
|
+
* - 3.1节 获取坐席信息
|
|
158
|
+
* - 3.2节 修改坐席状态接口,保证坐席状态在线
|
|
159
|
+
* tip: 该文档所有开放接口不支持跨域,如果直接通过ajax需要后端做转接(也可以直接用sdk创建的软电话实例的userAgentManager.requestOpenApi请求,该方法支持跨域同时自带请求签名)
|
|
160
|
+
* - example1:呼叫,也可以直接使用`await userAgentManager.callNumber('15018707394')`
|
|
161
|
+
* - 请求: http://[api-endpoint]/v1/task/importAgentCustomer
|
|
162
|
+
* - 相当于: userAgentManager.requestOpenApi({
|
|
163
|
+
* url: '/v1/task/importAgentCustomer',
|
|
164
|
+
* data: {
|
|
165
|
+
* agentTag: 'xxx', // 和agentId二选一
|
|
166
|
+
* agentId: 'xxx',
|
|
167
|
+
* callType: 1001,
|
|
168
|
+
* customers: [{ number: 15018707394 }]
|
|
169
|
+
* }
|
|
170
|
+
* })
|
|
171
|
+
* - example2:切换当前坐席小休,也可以直接使用`await userAgentManager.toggleNap(false)`
|
|
172
|
+
* - 请求: http://[api-endpoint]/v1/agent/updateAgentStatus
|
|
173
|
+
* - 相当于: userAgentManager.requestOpenApi({
|
|
174
|
+
* url: '/v1/agent/updateAgentStatus',
|
|
175
|
+
* data: {
|
|
176
|
+
* agentTag: 'xxx', // 和agentId二选一
|
|
177
|
+
* agentId: 'xxx',
|
|
178
|
+
* agentStatus: 3, // 3: 小休 1: 在线
|
|
179
|
+
* }
|
|
180
|
+
* })
|
|
181
|
+
* - example3:获取当前坐席信息,也可以直接使用`const angentInfo = await userAgentManager.getAgentInfo()`
|
|
182
|
+
* - 请求: http://[api-endpoint]/v1/agent/getAgent
|
|
183
|
+
* - 相当于: userAgentManager.requestOpenApi({
|
|
184
|
+
* url: '/v1/agent/getAgent',
|
|
185
|
+
* data: {
|
|
186
|
+
* agentTag: 'xxx', // 和agentId二选一
|
|
187
|
+
* agentId: 'xxx',
|
|
188
|
+
* }
|
|
189
|
+
* })
|
|
190
|
+
* 2. 点击连接外呼服务器
|
|
191
|
+
* 3. 在决策外呼一个任务外呼,确你的坐席账保号在任务配置的坐席组里;或手动主动外呼一个号码(`await userAgentManager.callNumber('15018707394')`)
|
|
192
|
+
* - tip 任务外呼:是先呼用户再呼坐席; 手动外呼:是先呼坐席在呼用户
|
|
193
|
+
* 4. 电话过来后会触发签入注册好的onInvite钩子,这个时候就可以接听,挂断,忽略,转人工,静音等等
|
|
194
|
+
* 5. 处理响应检测这块:
|
|
195
|
+
* - 建议接听/挂断/忽略只做一次性操作(不管失败成功与否无法二次操作,这个sip.js的局限性有一定关系,比如执行一次挂断后sip层的所有状态就被重置)
|
|
196
|
+
* - 建议转人工可以做多次操作(没转成功 可以操作转多次)
|
|
197
|
+
* - 通讯电话的大多实现也是这样,没有二次挂断/接听/忽略的操作。
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
document.addEventListener('DOMContentLoaded', async (event) => {
|
|
201
|
+
// try {
|
|
202
|
+
// await new Promise(((resolve, reject) => {
|
|
203
|
+
// navigator.getUserMedia({video: true,audio:true}, function onSuccess(stream) {
|
|
204
|
+
// resolve(stream)
|
|
205
|
+
// }, function onError(error) {
|
|
206
|
+
// reject(error)
|
|
207
|
+
// });
|
|
208
|
+
// }))
|
|
209
|
+
// alert('已点击允许,开启成功');
|
|
210
|
+
// } catch (error) {
|
|
211
|
+
// alert("错误:" + error);
|
|
212
|
+
// }
|
|
213
|
+
// const inputList = []
|
|
214
|
+
// const outputList = []
|
|
215
|
+
// ;(await navigator.mediaDevices.enumerateDevices()).forEach(item => {
|
|
216
|
+
// if (!item.label) return
|
|
217
|
+
// if (item.kind === 'audioinput') inputList.push(JSON.parse(JSON.stringify(item)))
|
|
218
|
+
// else if (item.kind === 'audiooutput') outputList.push(JSON.parse(JSON.stringify(item)))
|
|
219
|
+
// });
|
|
220
|
+
// alert('输入音频列表: ' + JSON.stringify(inputList))
|
|
221
|
+
// alert('输出音频列表: ' + JSON.stringify(outputList))
|
|
222
|
+
|
|
223
|
+
// 输入音频列表:
|
|
224
|
+
// [
|
|
225
|
+
// {"deviceld":"default",
|
|
226
|
+
// "kind":"audioinput",
|
|
227
|
+
// "label":"Default",
|
|
228
|
+
// "groupld":"7a8ef9d53aa7c8f2e5494ea603b1a825696ecfd7b965d69eabbc7b36ca24fde6"
|
|
229
|
+
// },
|
|
230
|
+
// {
|
|
231
|
+
// "deviceld":"dda9a37a56a1c8478824641c3218b30fab09035edccbe047c49f4b43cb273c3d",
|
|
232
|
+
// "kind":"audioinput",
|
|
233
|
+
// "label":"Speakerphone",
|
|
234
|
+
// "groupld":"96c1a27879018de7478bbbd4ac10d6c754c827e2e49b8946ccceea95af5381a7"
|
|
235
|
+
// },
|
|
236
|
+
// {"deviceld":"577a863742550f83e9fffc15465083087ce67abbcd0d1a69ac8a2d7a469c6f56",
|
|
237
|
+
// "kind":"audioinput",
|
|
238
|
+
// "label":"Headsetearpiece",
|
|
239
|
+
// "groupld": "af14e67d688dc0d4b40cb8162ce9794c3f9a0d15b5f5d34817292be3260f9127"
|
|
240
|
+
// }
|
|
241
|
+
// ]
|
|
242
|
+
|
|
243
|
+
// 输出音频列表:
|
|
244
|
+
// [
|
|
245
|
+
// {
|
|
246
|
+
// "deviceld":"default",
|
|
247
|
+
// "kind":"audiooutput",
|
|
248
|
+
// "label":"Default",
|
|
249
|
+
// "groupld": "default"
|
|
250
|
+
// }
|
|
251
|
+
// ]
|
|
252
|
+
const phoneRing = './antique_phone.mp3'
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 开启电话响铃
|
|
256
|
+
*/
|
|
257
|
+
const phoneRings = () => {
|
|
258
|
+
remoteAudio.src = phoneRing
|
|
259
|
+
playMedia('localAudio')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* 关闭电话响铃
|
|
264
|
+
*/
|
|
265
|
+
const stopPhoneRings = () => {
|
|
266
|
+
remoteAudio.src = ''
|
|
267
|
+
pauseMedia('localAudio')
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 获取dom
|
|
272
|
+
*/
|
|
273
|
+
const info = document.getElementById('info')
|
|
274
|
+
const ignore = document.getElementById('ignore')
|
|
275
|
+
const answer = document.getElementById('answer')
|
|
276
|
+
const hangUp = document.getElementById('hangUp')
|
|
277
|
+
const connectToServer = document.getElementById('connectToServer')
|
|
278
|
+
const call = document.getElementById('call')
|
|
279
|
+
const callButton = document.getElementById('callButton')
|
|
280
|
+
const nap = document.getElementById('nap')
|
|
281
|
+
const chatInfo = document.getElementById('chatInfo')
|
|
282
|
+
const online = document.getElementById('online')
|
|
283
|
+
const disconnect = document.getElementById('disconnect')
|
|
284
|
+
const sendStarDTMF = document.getElementById('sendStarDTMF')
|
|
285
|
+
const localVoiceAccess = document.getElementById('localVoiceAccess')
|
|
286
|
+
const remoteSoundAccess = document.getElementById('remoteSoundAccess')
|
|
287
|
+
const localSoundDisconnection = document.getElementById('localSoundDisconnection')
|
|
288
|
+
const remoteSoundDisconnection = document.getElementById('remoteSoundDisconnection')
|
|
289
|
+
const check = document.getElementById('check')
|
|
290
|
+
const volumn = document.getElementById('volumn')
|
|
291
|
+
const browserSoftphoneEnv = document.getElementById('browserSoftphoneEnv')
|
|
292
|
+
const localAudio = document.getElementById('localAudio')
|
|
293
|
+
const remoteAudio = document.getElementById('remoteAudio')
|
|
294
|
+
|
|
295
|
+
const {
|
|
296
|
+
UserAgentFactory,
|
|
297
|
+
UserAgent,
|
|
298
|
+
playMedia,
|
|
299
|
+
pauseMedia,
|
|
300
|
+
SessionState,
|
|
301
|
+
getMedia
|
|
302
|
+
} = softphone // 👈 sdk
|
|
303
|
+
|
|
304
|
+
/** 方式一:appKey + appSecret + agentTag 获取软电话链接代理实例 */
|
|
305
|
+
// 即采用系统的坐席信息接入外呼服务器实现软电话功能
|
|
306
|
+
// agentTag: 'agent1111', // 测试线 坐席维度定位 或用agentId
|
|
307
|
+
// appKey: 'ed96615038ffe394', // 测试线 企业维度定位
|
|
308
|
+
// appSecret: '9fb4aac7b1dbfbb21696bcd7a30cb382', // 测试线 企业维度定位
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* 1. 如果是海外环境新增这三个参数
|
|
312
|
+
* sgOpen: '1',
|
|
313
|
+
* sg: '1',
|
|
314
|
+
* openBaseUrl: '1',
|
|
315
|
+
* 2. 为了appSecret安全性,通过sign初始化,记得注册过期回调刷新sign
|
|
316
|
+
* sign: 'xxx', // 代替appSecret
|
|
317
|
+
* timestamp: 'xxx',
|
|
318
|
+
* async signOverdued() {
|
|
319
|
+
* return {
|
|
320
|
+
* timestamp: 'www',
|
|
321
|
+
* sign: 'qqq',
|
|
322
|
+
* }
|
|
323
|
+
* },
|
|
324
|
+
* 3. sign是openApi授权
|
|
325
|
+
* token是gatewayApi授权.gatewayApi授权用来获取来电详情,对话记录等信息,如果不需要获取对话记录可以不传
|
|
326
|
+
* 获取token可以参考下方注释, 记得注册过期回调刷新token,跟sign一样
|
|
327
|
+
* token: 'xxx', // 代替appSecret
|
|
328
|
+
* async tokenOverdued() {
|
|
329
|
+
* return {
|
|
330
|
+
* token: 'qqq',
|
|
331
|
+
* }
|
|
332
|
+
* },
|
|
333
|
+
*/
|
|
334
|
+
const userAgentManager = UserAgentFactory.getUserAgentManager({
|
|
335
|
+
// sgOpen: '1',
|
|
336
|
+
// sg: '1',
|
|
337
|
+
// openBaseUrl: '1',
|
|
338
|
+
// logLevel: 'debug', // 日志等级,默认error
|
|
339
|
+
agentTag: '15018707394', // 生产线 坐席维度定位
|
|
340
|
+
appKey: '032d44009bff1752', // 生产线 企业维度定位
|
|
341
|
+
appSecret: '4c7304c94c5e8725613516d4d6db679b', // 生产线 企业维度定位 这里直接使用appSecret做演示
|
|
342
|
+
// agentId: '9870', // 生产线 坐席维度定位
|
|
343
|
+
// appKey: 'd85085f084e71477', // 生产线 企业维度定位
|
|
344
|
+
// appSecret: '1df8fdeaa960c7fefd8da39135a693ae', // 生产线 企业维度定位 这里直接使用appSecret做演示
|
|
345
|
+
// agentId: '2357', // 生产线 坐席维度定位
|
|
346
|
+
// appKey: '9297afc05dfe1704', // 生产线 企业维度定位
|
|
347
|
+
// appSecret: '616e860760cbd34deb1970390bf82659', // 生产线 企业维度定位 这里直接使用appSecret做演示
|
|
348
|
+
// refreshSpeekVolumn(value) { // 当来电接通时,实时查看坐席说话的音量实际传送到给对方的音量大小
|
|
349
|
+
// volumn.innerText = value
|
|
350
|
+
// },
|
|
351
|
+
// refreshRequirementCheck(value) { // 权限网络检测
|
|
352
|
+
// browserSoftphoneEnv.innerText = JSON.stringify(value, null, 2)
|
|
353
|
+
// },
|
|
354
|
+
// refreshChat(info) { // 刷新会话记录
|
|
355
|
+
// chatInfo.innerText = JSON.stringify(info, null, 2)
|
|
356
|
+
// },
|
|
357
|
+
// refreshChatErrorCallback(e) {
|
|
358
|
+
// console.log(e)
|
|
359
|
+
// },
|
|
360
|
+
// sessionDescriptionHandlerFactoryOptions: { // 如果fs开启b2b没模式不需要传,开启p2p模式要传
|
|
361
|
+
// iceGatheringTimeout: 2000,
|
|
362
|
+
// peerConnectionConfiguration: {
|
|
363
|
+
// iceServers: [{ urls: "stun:stun.l.google.com:19302" }], // stun:stun.freeswitch.org
|
|
364
|
+
// }
|
|
365
|
+
// },
|
|
366
|
+
// enableChatInfoPush: true // 开启会话记录实时推送,开启后,当任务外呼时,可以实时获取通话记录信息,实时回调到refreshChat,中途出现任何异常会回调refreshChatErrorCallback
|
|
367
|
+
})
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 获取token & 实时查到tag等自定义注入字段对接步骤:
|
|
371
|
+
* 1. sdk来电获取来电相关id
|
|
372
|
+
* onInvite: (invitation: Invitation) => {
|
|
373
|
+
* // invitation.incomingInviteRequest.message.headers['X-Numberid']?.[0].raw
|
|
374
|
+
* // invitation.incomingInviteRequest.message.headers['X-Taskid']?.[0].raw
|
|
375
|
+
* // 根据id查询tag或业务信息
|
|
376
|
+
*
|
|
377
|
+
* 客户后端对接gateway Api后提供给客户前端接口及时查询tag(后续其他自定义字段也一并在这里)
|
|
378
|
+
* 2. post: https://[gateway-endpoint]/authority/accessToken/openApi 获取token
|
|
379
|
+
* 参数:
|
|
380
|
+
* corpId: this.#appKey,
|
|
381
|
+
* sid: this.#agentId,
|
|
382
|
+
* secret: this.#appSecret,
|
|
383
|
+
* seatOnline: false
|
|
384
|
+
* xhr.setRequestHeader('x-app-code', 3)
|
|
385
|
+
* xhr.setRequestHeader("content-type", "application/json")
|
|
386
|
+
*
|
|
387
|
+
* 返回: {
|
|
388
|
+
* "token": "a1YyMmFtdGRCM0FiY0QvVGd2dXg4OGIzTHNiK2pSM3BWeHZSbVQrSUhaVT0=", // gateway token ,也可以直接传给sdk,之后通过sdk内置可以直接获取来电详情和对话记录等信息
|
|
389
|
+
* "userId": 11976,
|
|
390
|
+
* "oldId": 8292,
|
|
391
|
+
* "companyId": 1576,
|
|
392
|
+
* "chainId": 0,
|
|
393
|
+
* "orgId": 1576,
|
|
394
|
+
* "userType": 4,
|
|
395
|
+
* "account": "zoujh",
|
|
396
|
+
* "userName": "zjh",
|
|
397
|
+
* "defaultPassword": "",
|
|
398
|
+
* "userTypeName": "坐席"
|
|
399
|
+
* }
|
|
400
|
+
*
|
|
401
|
+
* 3. post: https://[gateway-endpoint]/task-aggre/callCenterNumber/get 获取来电详情
|
|
402
|
+
* xhr.setRequestHeader('x-token', token)
|
|
403
|
+
* xhr.setRequestHeader('seatsToken', token)
|
|
404
|
+
* xhr.setRequestHeader('x-authority-token', token)
|
|
405
|
+
* xhr.setRequestHeader('x-app-code', 3)
|
|
406
|
+
* xhr.setRequestHeader("content-type", "application/json")
|
|
407
|
+
* 参数:
|
|
408
|
+
* id: numberId,
|
|
409
|
+
* taskId: taskId
|
|
410
|
+
*
|
|
411
|
+
* 返回:
|
|
412
|
+
* {
|
|
413
|
+
* "status": 200,
|
|
414
|
+
* "code": 200,
|
|
415
|
+
* "message": "success",
|
|
416
|
+
* "data": {
|
|
417
|
+
* "tag": 'xxx', // 这是有相关自定义参数
|
|
418
|
+
* "id": 703,
|
|
419
|
+
* "number": "15018707394",
|
|
420
|
+
* "numberMd5": "77c357c1660cb20b534be8d4ba02cd25",
|
|
421
|
+
* "params": "{}", // 这是有相关自定义参数
|
|
422
|
+
* "intentTag": "C",
|
|
423
|
+
* "state": 10,
|
|
424
|
+
* "description": "hangup",
|
|
425
|
+
* "recycle": 3,
|
|
426
|
+
* "callid": "431a6218-5d40-4312-90d7-bb63e0345774",
|
|
427
|
+
* "bill": 4820,
|
|
428
|
+
* "duration": 9360,
|
|
429
|
+
* "bridgeNumber": "9288",
|
|
430
|
+
* "status": "invalid answer",
|
|
431
|
+
* "callTotal": 0,
|
|
432
|
+
* "smsStatus": 0,
|
|
433
|
+
* "repeatCount": 0,
|
|
434
|
+
* "lineRepeatCount": 0,
|
|
435
|
+
* "invalidRepeatCount": 0,
|
|
436
|
+
* "totalRepeatCount": 0,
|
|
437
|
+
* "createTime": 1730975822000,
|
|
438
|
+
* "keywords": "",
|
|
439
|
+
* "gatewayId": 4232,
|
|
440
|
+
* "callLog": "[{\"callTime\":\"Nov 07, 2024 10:37:12 AM\",\"status\":\"answer\",\"statusName\":\"已接听\",\"gatewayId\":4232,\"gatewayName\":\"开发人员专用线路-邹家和-1\",\"callServer\":\"server24\"}]",
|
|
441
|
+
* "extensionNumber": "9288",
|
|
442
|
+
* "sid": 8292,
|
|
443
|
+
* "aiBill": 4820,
|
|
444
|
+
* "bridgeBill": 0,
|
|
445
|
+
* "transferStatus": 20,
|
|
446
|
+
* "companyId": 1576,
|
|
447
|
+
* "callType": 1,
|
|
448
|
+
* "answerTransferType": 2,
|
|
449
|
+
* "statusCode": 20,
|
|
450
|
+
* "batchId": "1669921261571",
|
|
451
|
+
* "config": "{\"expNum\":\"94aikf\",\"whatsappStatus\":0,\"whatsappRead\":2,\"personality\":{},\"templateId\":25662,\"ringAlready\":1,\"originDa\":\"ringback\",\"billByHangup\":0,\"seatsGroupId\":1499}",
|
|
452
|
+
* "alterTime": 1730975843000,
|
|
453
|
+
* "provinceCode": "440000",
|
|
454
|
+
* "cityCode": "440100",
|
|
455
|
+
* "telecomOperator": "中国移动"
|
|
456
|
+
* }
|
|
457
|
+
* }
|
|
458
|
+
*/
|
|
459
|
+
|
|
460
|
+
/** 方式二:extensionNumber + extPassword + wsProtocol + wsRegisterAddress 获取软电话链接代理实例 */
|
|
461
|
+
// 即直接通过【分机号】和【分机密码】以及【外呼服务器地址】实现软电话的直接签入
|
|
462
|
+
// 生产环境如果采用分机维度的分机信息可以通过agentTag调用openapi获取
|
|
463
|
+
// const extensionNumber = '2839' // 测试线 分机账号
|
|
464
|
+
// const extPassword = 'zjh13542240708' // 测试线 分机密码
|
|
465
|
+
// const wsProtocol = 'ws' // 测试线外呼服务器地址协议
|
|
466
|
+
// const wsRegisterAddress = '192.168.31.239:5066' // 测试线 外呼服务器地址
|
|
467
|
+
// const sipServerHost = `sip:${extensionNumber}@${wsRegisterAddress}` // fs地址
|
|
468
|
+
// const wsAddress = `${wsProtocol}://${wsRegisterAddress}` // socket地址
|
|
469
|
+
|
|
470
|
+
// const extensionNumber = '2510' // 生产线 分机账号
|
|
471
|
+
// const extPassword = 'zengrongzhi123' // 生产线 分机密码
|
|
472
|
+
// const wsProtocol = 'ws' // 生产线 外呼服务器地址协议
|
|
473
|
+
// const wsRegisterAddress = '192.168.31.239:5066' // 生产线 外呼服务器地址
|
|
474
|
+
// const sipServerHost = `sip:${extensionNumber}@${wsRegisterAddress}` // fs地址
|
|
475
|
+
// const wsAddress = `${wsProtocol}://${wsRegisterAddress}` // socket地址
|
|
476
|
+
|
|
477
|
+
// const extensionNumber = '1001' // 生产线 分机账号
|
|
478
|
+
// const extPassword = '946666' // 生产线 分机密码
|
|
479
|
+
// const wsProtocol = 'ws' // 生产线 外呼服务器地址协议
|
|
480
|
+
// const wsRegisterAddress = '192.168.0.92:5066' // 生产线 外呼服务器地址
|
|
481
|
+
// const sipServerHost = `sip:${extensionNumber}@${wsRegisterAddress}` // fs地址
|
|
482
|
+
// const wsAddress = `${wsProtocol}://${wsRegisterAddress}` // socket地址
|
|
483
|
+
//
|
|
484
|
+
// const userAgentManager = UserAgentFactory.getUserAgentManager({
|
|
485
|
+
// authorizationPassword: extPassword, // 分机密码
|
|
486
|
+
// authorizationUsername: extensionNumber, // 分机账号
|
|
487
|
+
// uri: UserAgent.makeURI(sipServerHost), // fs地址
|
|
488
|
+
// transportOptions: {
|
|
489
|
+
// server: wsAddress // socket地址
|
|
490
|
+
// },
|
|
491
|
+
// logLevel: 'error', // 日志等级,默认error
|
|
492
|
+
// // viaHost, // fp.umd.min.js生成的唯一值,用来配查呼叫问题,可以不传,内部会自动生成
|
|
493
|
+
// contactName: extensionNumber, // sip协议要求,同分机账号即可,注意类型为string, 不传默认同分机账号
|
|
494
|
+
// clusterMode: true,
|
|
495
|
+
// reClusterRegisterTimeout: 1000,
|
|
496
|
+
// reClusterConnectTimeout: 1000,
|
|
497
|
+
// // refreshSpeekVolumn(value) { // 当来电接通时,实时查看坐席说话的音量实际传送到给对方的音量大小
|
|
498
|
+
// // volumn.innerText = value
|
|
499
|
+
// // },
|
|
500
|
+
// // refreshRequirementCheck(value) { // 权限网络检测
|
|
501
|
+
// // browserSoftphoneEnv.innerText = JSON.stringify(value, null, 2)
|
|
502
|
+
// // },
|
|
503
|
+
// // refreshChat(info) {
|
|
504
|
+
// // chatInfo.innerText = JSON.stringify(info, null, 2)
|
|
505
|
+
// // },
|
|
506
|
+
// // refreshChatErrorCallback(e) {
|
|
507
|
+
// // console.log(e)
|
|
508
|
+
// // },
|
|
509
|
+
// // enableChatInfoPush: true // 开启会话记录实时推送,开启后,当任务外呼时,可以实时获取通话记录信息,实时回调到refreshChat,中途出现任何异常会回调refreshChatErrorCallback
|
|
510
|
+
// })
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* 获取坐席当前状态信息(注意牵扯到openAPI的信息需要angentId,即方式一。如果通过方式二需要手动使用userAgentManager.requestOpenApi获取,即上述example3
|
|
514
|
+
*/
|
|
515
|
+
const angentInfo = await userAgentManager.getAgentInfo()
|
|
516
|
+
if (angentInfo.code === 200) { // 正确响应
|
|
517
|
+
info.innerText = `1. ${JSON.stringify(angentInfo.data, null, 2)}`
|
|
518
|
+
if (angentInfo.data.agentStatus === 1) { // 页面按钮的禁用状态
|
|
519
|
+
nap.disabled = false
|
|
520
|
+
online.disabled = true // 可切换在线
|
|
521
|
+
} else {
|
|
522
|
+
nap.disabled = true // 可切换小休
|
|
523
|
+
online.disabled = false
|
|
524
|
+
}
|
|
525
|
+
} else {
|
|
526
|
+
alert(angentInfo.message)
|
|
527
|
+
throw new Error(angentInfo)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* 签入
|
|
532
|
+
*/
|
|
533
|
+
const connect = async () => {
|
|
534
|
+
try {
|
|
535
|
+
await userAgentManager.prepareUserAgent(
|
|
536
|
+
{ // config
|
|
537
|
+
refresh(path, value) { // 当软电话状态变化时会实时刷新这个方法
|
|
538
|
+
console.log(path, value)
|
|
539
|
+
},
|
|
540
|
+
registererOptions: { // 设置会话多长时间过期,然后自动重签, 单位秒
|
|
541
|
+
expires: 10 * 60
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
{ // event
|
|
545
|
+
onInvite(invitation) { // 当有外呼过来
|
|
546
|
+
// const info = userAgentManager.getCallNumberDetail({
|
|
547
|
+
// id: Number(userAgentManager.businessAttribute.numberId), // 根据你需要哪通电话来,这里在来电时获取当前来电详情
|
|
548
|
+
// taskId: userAgentManager.businessAttribute.taskId, // 如果不是任务外呼不用传
|
|
549
|
+
// })
|
|
550
|
+
// console.log(info, '========')
|
|
551
|
+
setTimeout(() => {
|
|
552
|
+
acceptInvite()
|
|
553
|
+
}, 0)
|
|
554
|
+
console.log(userAgentManager.getSenders) // 用来实现换流或切换设备
|
|
555
|
+
console.log(userAgentManager.businessAttribute) // 自定义sip头通讯,业务信息透传
|
|
556
|
+
phoneRings() // 播放模拟来电响铃
|
|
557
|
+
invitation.stateChange.addListener((state) => { // 一旦接听(执行accept),监听会话的生命周期
|
|
558
|
+
switch (state) {
|
|
559
|
+
case SessionState.Initial:
|
|
560
|
+
break
|
|
561
|
+
case SessionState.Establishing:
|
|
562
|
+
break
|
|
563
|
+
case SessionState.Established: // session建立后就可拿到webrtc的各种基础api,如userAgentManager.getPeerConnection(),如userAgentManager.getSenders()等等
|
|
564
|
+
const mediaElement1 = getMedia('remoteAudio')
|
|
565
|
+
mediaElement1.srcObject = userAgentManager.getStream() // 获取流
|
|
566
|
+
mediaElement1.play() // 把softphone流导入到audio接入用户语音
|
|
567
|
+
hangUp.disabled = false
|
|
568
|
+
break
|
|
569
|
+
case SessionState.Terminating:
|
|
570
|
+
case SessionState.Terminated: // 在挂断电话时候会执行
|
|
571
|
+
const mediaElement2 = getMedia('remoteAudio') // 获取audio dom
|
|
572
|
+
mediaElement2.srcObject = null
|
|
573
|
+
mediaElement2.pause() // 释放audio
|
|
574
|
+
stopPhoneRings()
|
|
575
|
+
|
|
576
|
+
ignore.disabled = true
|
|
577
|
+
answer.disabled = true
|
|
578
|
+
hangUp.disabled = true
|
|
579
|
+
localSoundDisconnection.disabled = true
|
|
580
|
+
remoteSoundDisconnection.disabled = true
|
|
581
|
+
localVoiceAccess.disabled = true
|
|
582
|
+
remoteSoundAccess.disabled = true
|
|
583
|
+
break
|
|
584
|
+
default:
|
|
585
|
+
throw new Error('Unknown session state.')
|
|
586
|
+
}
|
|
587
|
+
})
|
|
588
|
+
ignore.disabled = false
|
|
589
|
+
answer.disabled = false
|
|
590
|
+
localSoundDisconnection.disabled = false
|
|
591
|
+
remoteSoundDisconnection.disabled = false
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
) // 👈 一键签入
|
|
595
|
+
} catch (errorInfo) { // 签入容错提示处理
|
|
596
|
+
if (errorInfo.message.indexOf('Invalid WebSocket Server URL') > -1
|
|
597
|
+
|| errorInfo.message.indexOf('Invalid scheme in WebSocket Server URL') > -1) {
|
|
598
|
+
alert('签入配置地址不正确,无法连通完成签入')
|
|
599
|
+
} else if (errorInfo.message === 'sip register fail with code 503' ||
|
|
600
|
+
(errorInfo.message.indexOf('WebSocket closed') > -1 && errorInfo.message.indexOf('code: 1006') > -1)) {
|
|
601
|
+
alert('软电话服务器不可用,签入失败')
|
|
602
|
+
} else {
|
|
603
|
+
alert(errorInfo.message)
|
|
604
|
+
}
|
|
605
|
+
throw errorInfo
|
|
606
|
+
}
|
|
607
|
+
disconnect.disabled = false
|
|
608
|
+
connectToServer.disabled = true
|
|
609
|
+
ignore.disabled = true
|
|
610
|
+
answer.disabled = true
|
|
611
|
+
hangUp.disabled = true
|
|
612
|
+
localSoundDisconnection.disabled = true
|
|
613
|
+
remoteSoundDisconnection.disabled = true
|
|
614
|
+
localVoiceAccess.disabled = true
|
|
615
|
+
remoteSoundAccess.disabled = true
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* 签出
|
|
620
|
+
*/
|
|
621
|
+
const disConnect = async () => {
|
|
622
|
+
await userAgentManager.dispose() // 👈 一个方法安全销毁,一键签出
|
|
623
|
+
connectToServer.disabled = false
|
|
624
|
+
disconnect.disabled = true
|
|
625
|
+
ignore.disabled = true
|
|
626
|
+
answer.disabled = true
|
|
627
|
+
hangUp.disabled = true
|
|
628
|
+
localSoundDisconnection.disabled = true
|
|
629
|
+
remoteSoundDisconnection.disabled = true
|
|
630
|
+
localVoiceAccess.disabled = true
|
|
631
|
+
remoteSoundAccess.disabled = true
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* 按*号转人工,返回promise,可以做loading效果做点击锁
|
|
636
|
+
*/
|
|
637
|
+
|
|
638
|
+
const sendStarDtmf = async () => {
|
|
639
|
+
// 由于 【坐席外呼类型】 没有配置要不要转人工的入口
|
|
640
|
+
// 而:【坐席快速外呼】 不需要 转人工;【坐席ai外呼】 一定要 转人工;其他外呼类型根据userAgentManager.notNeedSendStarDtmf(true不需要,false需要,即本质是根据配置),如任务类型外呼根据任务的配置
|
|
641
|
+
// userAgentManager.getFastOutboundCall判断是否坐席快速外呼(true是false否)
|
|
642
|
+
// userAgentManager.getSitOutboundCall判断是否坐席ai外呼(true是false否)
|
|
643
|
+
if ((!userAgentManager.notNeedSendStarDtmf() && !userAgentManager.getFastOutboundCall()) || userAgentManager.getSitOutboundCall()) {
|
|
644
|
+
await new Promise((resolve, reject) => {
|
|
645
|
+
const fn = async () => {
|
|
646
|
+
await userAgentManager.sendStarDtmf({
|
|
647
|
+
requestDelegate: {
|
|
648
|
+
onAccept () { // 👈 转人工响应成功
|
|
649
|
+
resolve(true)
|
|
650
|
+
},
|
|
651
|
+
async onReject (info) { //👈 转人工响应超时或失败
|
|
652
|
+
alert('转人工响应失败:' + info.message?.statusCode + ':' + info.message?.reasonPhrase)
|
|
653
|
+
reject(new Error(info.message.statusCode + ':' + info.message.reasonPhrase))
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}) // 👈 一键转人工,await的下行代码被执行说明成功发出转人工信令
|
|
657
|
+
sendStarDTMF.disabled = true
|
|
658
|
+
}
|
|
659
|
+
fn()
|
|
660
|
+
})
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* 挂断
|
|
666
|
+
*/
|
|
667
|
+
const hangUpInvite = async () => {
|
|
668
|
+
await new Promise((resolve, reject) => {
|
|
669
|
+
const fn = async () => {
|
|
670
|
+
await userAgentManager.hangUpInvite({
|
|
671
|
+
scoutResponse: true, // 标识是手动挂断,手动挂断无需等待响应,信号成功发出立即重置会话状态。
|
|
672
|
+
byeOptions: {
|
|
673
|
+
requestDelegate: {
|
|
674
|
+
onAccept (response) { // 👈 挂断响应成功
|
|
675
|
+
resolve(response)
|
|
676
|
+
},
|
|
677
|
+
onReject: (response) => { // 👈 挂断响应超时或失败
|
|
678
|
+
alert('挂断响应失败: ' + response?.message?.statusCode + ': ' + response?.message?.reasonPhrase) // 会话终止
|
|
679
|
+
reject(new Error(response?.message?.statusCode + ': ' + response?.message?.reasonPhrase))
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
},
|
|
683
|
+
}) // 👈 一键挂断, 下行代码执行,说明挂端信令成功发出
|
|
684
|
+
stopPhoneRings() // 暂停来电铃声,正常挂断不需要处理铃声(因为已经接听,会话已建立,才能挂断),这里做个兜底
|
|
685
|
+
ignore.disabled = true
|
|
686
|
+
answer.disabled = true
|
|
687
|
+
hangUp.disabled = true
|
|
688
|
+
localSoundDisconnection.disabled = true
|
|
689
|
+
remoteSoundDisconnection.disabled = true
|
|
690
|
+
localVoiceAccess.disabled = true
|
|
691
|
+
remoteSoundAccess.disabled = true
|
|
692
|
+
}
|
|
693
|
+
fn()
|
|
694
|
+
})
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* 忽略
|
|
698
|
+
*/
|
|
699
|
+
const ignoreInvite = async () => {
|
|
700
|
+
stopPhoneRings() // 暂停来电铃声
|
|
701
|
+
await userAgentManager.ignoreInvite() // 👈 一键忽略,下行代码被执行说明忽略信令成功发出
|
|
702
|
+
ignore.disabled = true
|
|
703
|
+
answer.disabled = true
|
|
704
|
+
hangUp.disabled = true
|
|
705
|
+
localSoundDisconnection.disabled = true
|
|
706
|
+
remoteSoundDisconnection.disabled = true
|
|
707
|
+
localVoiceAccess.disabled = true
|
|
708
|
+
remoteSoundAccess.disabled = true
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 接听
|
|
712
|
+
*/
|
|
713
|
+
const acceptInvite = async () => {
|
|
714
|
+
stopPhoneRings() // 暂停来电铃声
|
|
715
|
+
try {
|
|
716
|
+
await new Promise((resolve, reject) => {
|
|
717
|
+
const fn = async () => {
|
|
718
|
+
try {
|
|
719
|
+
await userAgentManager.acceptInvite({
|
|
720
|
+
onAck (info) { // 👈 接听响应成功
|
|
721
|
+
resolve(info)
|
|
722
|
+
},
|
|
723
|
+
onAckTimeout () { // 👈 接听响应失败
|
|
724
|
+
reject(new Error('接听响应失败'))
|
|
725
|
+
}
|
|
726
|
+
}) // 👈 一键接听,下行代码被执行说明接听信令被成功发出
|
|
727
|
+
} catch (e) {
|
|
728
|
+
console.log('accept error:', e.message)
|
|
729
|
+
reject(e)
|
|
730
|
+
throw e
|
|
731
|
+
}
|
|
732
|
+
console.log('接听信令被成功发出')
|
|
733
|
+
}
|
|
734
|
+
fn()
|
|
735
|
+
})
|
|
736
|
+
// 接听信令不能被发出,可能原因如下:
|
|
737
|
+
} catch (error) { // 接听容错提示处理
|
|
738
|
+
if (error.message === 'Requested device not found'
|
|
739
|
+
|| error.message === 'The object can not be found here.') { // 设备问题
|
|
740
|
+
alert('未检测到耳麦设备,已自动断开来电')
|
|
741
|
+
throw error
|
|
742
|
+
} else if (error.message === 'The request is not allowed by the user agent or the platform in the current context.'
|
|
743
|
+
|| error.message === 'Media devices not available in insecure contexts.') { // 设备权限和浏览器api权限问题
|
|
744
|
+
if (navigator.userAgent.indexOf('Firefox') > -1) {
|
|
745
|
+
alert('软电话功能需要获取麦克风权限,如果没有麦克风权限或者http域名未配置insecure contexts都会引起连接会话立即终止,如果当前浏览器访问的地址是https协议,大部分连接会话终止的原因是没有麦克风权限')
|
|
746
|
+
} else {
|
|
747
|
+
alert('浏览器webrtc功能默认需要网站域名升级到https,如果当前域名为http,请配置浏览器insecure以指定域名白名单使用webrtc api,此时才可获取设备等权限以使用软电话功能')
|
|
748
|
+
}
|
|
749
|
+
throw error
|
|
750
|
+
} else if (error.message === 'Permission denied') { // 麦克风权限问题
|
|
751
|
+
alert('没有麦克风权限,请开启')
|
|
752
|
+
throw error
|
|
753
|
+
} else if (error.message === 'RTCPeerConnection is not defined') { // 浏览器版本问题
|
|
754
|
+
alert('当前浏览器版本过低不支持webrtc,推荐使用新版的谷歌或360或edge浏览器')
|
|
755
|
+
throw error
|
|
756
|
+
}
|
|
757
|
+
throw error
|
|
758
|
+
}
|
|
759
|
+
// 由于 【坐席外呼类型】 没有配置要不要转人工的入口
|
|
760
|
+
// 而:【坐席快速外呼】 不需要 转人工;【坐席ai外呼】 一定要 转人工;其他外呼类型根据userAgentManager.notNeedSendStarDtmf(true不需要,false需要,根据配置),如任务类型外呼根据任务配置
|
|
761
|
+
// userAgentManager.getFastOutboundCall判断是否坐席快速外呼(true是false否)
|
|
762
|
+
// userAgentManager.getSitOutboundCall判断是否坐席ai外呼(true是false否)
|
|
763
|
+
if ((!userAgentManager.notNeedSendStarDtmf() && !userAgentManager.getFastOutboundCall()) || userAgentManager.getSitOutboundCall()) {
|
|
764
|
+
sendStarDTMF.disabled = false // 打开页面转人工功能
|
|
765
|
+
}
|
|
766
|
+
hangUp.disabled = false
|
|
767
|
+
answer.disabled = true
|
|
768
|
+
ignore.disabled = true
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* 本地声音推流通道 -> 默认开
|
|
773
|
+
* 手动打开 本地声音推流通道(打开本地声音发送)
|
|
774
|
+
*/
|
|
775
|
+
const unMuteLocalAudio = () => {
|
|
776
|
+
localVoiceAccess.disabled = true
|
|
777
|
+
localSoundDisconnection.disabled = false
|
|
778
|
+
userAgentManager.unMuteLocalAudio() // 👈 一键
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* 本地声音推流通道 -> 默认开
|
|
782
|
+
* 手动关闭 本地声音推流通道(关闭本地声音发送)
|
|
783
|
+
*/
|
|
784
|
+
const muteLocalAudio = () => {
|
|
785
|
+
remoteSoundAccess.disabled = true
|
|
786
|
+
remoteSoundDisconnection.disabled = false
|
|
787
|
+
userAgentManager.muteLocalAudio() // 👈 一键
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* 远端声音拉流通道 -> 默认开
|
|
791
|
+
* 手动打开 远端声音拉流通道(用以打开远端声音接收)
|
|
792
|
+
*/
|
|
793
|
+
const unMuteRemoteAudio = () => {
|
|
794
|
+
localSoundDisconnection.disabled = true
|
|
795
|
+
localVoiceAccess.disabled = false
|
|
796
|
+
userAgentManager.unMuteRemoteAudio() // 👈 一键
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* 远端声音拉流通道 -> 默认开
|
|
800
|
+
* 手动关闭 远端声音拉流通道(用以关闭远端声音接收)
|
|
801
|
+
*/
|
|
802
|
+
const muteRemoteAudio = () => {
|
|
803
|
+
remoteSoundDisconnection.disabled = true
|
|
804
|
+
remoteSoundAccess.disabled = false
|
|
805
|
+
userAgentManager.muteRemoteAudio() // 👈 一键
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* 呼叫
|
|
810
|
+
* - 手机号外呼用openapi,如 userAgentManager.callNumber 或 userAgentManager.requestOpenApi({
|
|
811
|
+
* url: '/v1/task/importAgentCustomer',
|
|
812
|
+
* data: {
|
|
813
|
+
* agentTag: 'xxx',
|
|
814
|
+
* agentId: 'xxx',
|
|
815
|
+
* callType: 1001,
|
|
816
|
+
* customers: [{ number: 15018707394 }]
|
|
817
|
+
* }
|
|
818
|
+
* })
|
|
819
|
+
* - 分机外呼用invite, 如await userAgentManager.invite({
|
|
820
|
+
* // uri: 'xxx' // 如果是不同外呼服务器的分机,使用uri,格式同上 sipServerHost
|
|
821
|
+
* extension: call.value // 同个外呼服务器地址的分机 例如上述分机账号 extensionNumber
|
|
822
|
+
* })
|
|
823
|
+
*
|
|
824
|
+
*/
|
|
825
|
+
const callPhone = async () => {
|
|
826
|
+
const callInfo = await userAgentManager.callNumber(call.value) // 👈 一键
|
|
827
|
+
// const callInfo = await userAgentManager.requestOpenApi({ // 主动外呼
|
|
828
|
+
// url: '/v1/task/importAgentCustomer',
|
|
829
|
+
// data: {
|
|
830
|
+
// agentTag: '15018707394',
|
|
831
|
+
// callType: 1001,
|
|
832
|
+
// customers: [{
|
|
833
|
+
// tag: '测试tag',
|
|
834
|
+
// number: 18617381945
|
|
835
|
+
// }]
|
|
836
|
+
// }
|
|
837
|
+
// })
|
|
838
|
+
if (callInfo.code === 200) { // 正确响应
|
|
839
|
+
alert('呼叫成功')
|
|
840
|
+
} else {
|
|
841
|
+
alert(callInfo.message)
|
|
842
|
+
}
|
|
843
|
+
// await userAgentManager.invite({
|
|
844
|
+
// extension: call.value
|
|
845
|
+
// })
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* 切换坐席进入小休
|
|
850
|
+
*/
|
|
851
|
+
const toggleNap = async () => {
|
|
852
|
+
const napInfo = await userAgentManager.toggleNap(true) // 👈 一键
|
|
853
|
+
if (napInfo.code === 200) { // 正确响应
|
|
854
|
+
nap.disabled = true
|
|
855
|
+
online.disabled = false
|
|
856
|
+
alert('进入小休成功')
|
|
857
|
+
} else {
|
|
858
|
+
alert(napInfo.message)
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* 切换坐席进入在线
|
|
864
|
+
*/
|
|
865
|
+
const toggleOnline = async () => {
|
|
866
|
+
const napInfo = await userAgentManager.toggleNap(false) // 👈 一键
|
|
867
|
+
if (napInfo.code === 200) { // 正确响应
|
|
868
|
+
nap.disabled = false
|
|
869
|
+
online.disabled = true
|
|
870
|
+
alert('设置坐席在线成功')
|
|
871
|
+
} else {
|
|
872
|
+
alert(napInfo.message)
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* 环境检测
|
|
878
|
+
*/
|
|
879
|
+
const envCheck = () => {
|
|
880
|
+
userAgentManager.softphoneEnvCheck() // 👈 一键检测,检测出结果后会回调在生成软电话实例时注册的钩子 refreshRequirementCheck
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/** 各种操作按钮 注册事件开始 */
|
|
884
|
+
ignore.addEventListener('click', ignoreInvite) // 忽略
|
|
885
|
+
answer.addEventListener('click', acceptInvite) // 接听
|
|
886
|
+
hangUp.addEventListener('click', hangUpInvite) // 挂断
|
|
887
|
+
connectToServer.addEventListener('click', connect) // 签入
|
|
888
|
+
disconnect.addEventListener('click', disConnect) // 签出
|
|
889
|
+
sendStarDTMF.addEventListener('click', sendStarDtmf) // 转人工
|
|
890
|
+
localVoiceAccess.addEventListener('click', unMuteLocalAudio) // 打开本地声音发送
|
|
891
|
+
remoteSoundAccess.addEventListener('click', muteLocalAudio) // 关闭本地声音发送
|
|
892
|
+
localSoundDisconnection.addEventListener('click', unMuteRemoteAudio) // 打开远端声音接收
|
|
893
|
+
remoteSoundDisconnection.addEventListener('click', muteRemoteAudio) // 关闭远端声音接收
|
|
894
|
+
callButton.addEventListener('click', callPhone) // 呼叫
|
|
895
|
+
nap.addEventListener('click', toggleNap) // 切换坐席进入小休
|
|
896
|
+
online.addEventListener('click', toggleOnline) // 切换坐席进入在线
|
|
897
|
+
check.addEventListener('click', envCheck) // 执行软电话必备的权限&网络环境检测
|
|
898
|
+
/** 注册事件结束 */
|
|
899
|
+
})
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
</script>
|
|
903
|
+
</body>
|
|
904
|
+
</html>
|