@94ai/softphone 4.0.1

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/README.md ADDED
@@ -0,0 +1,941 @@
1
+ # 前言
2
+
3
+ 94ai软电话前端SDK,框架无关(支持vue,react,angular等)
4
+
5
+ 项目参考demo: https://gitee.com/softphone-demo(有权限问题,具体找94智能开发同事拉入团队组织即可)
6
+
7
+ 本地调试:
8
+ yarn add sip.js@portal:../SIP修改v2.js
9
+
10
+ # 安装
11
+ 94智能sdk目前暂不在公网公布sdk源码,如有需要,可以联系94智能开发同事加入团队,具体步骤:
12
+
13
+ ## 获取加入团队邀请链接
14
+ 1. 向94智能开发同事申请到【加入到团队的连接】,如:
15
+ https://account-devops.aliyun.com/account/invite?sign=ab965d478dbed5dad3cc145a5a5b406c&next_url=https%3A%2F%2Fpackages.aliyun.com%3ForgId%3D644f755a97d94d909e43534c
16
+
17
+ 2. 进入上述链接后,进到阿里云登录页
18
+
19
+ - 如果你是主账号,直接登录即可:
20
+
21
+ ![img.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img.png)
22
+
23
+ - 如果你是RAM子账号,选择下面RAM用户登录:
24
+
25
+ ![img_1.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_1.png)
26
+
27
+ 进入RAM 用户登录页登录即可:
28
+
29
+ ![img_2.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_2.png)
30
+
31
+ ## 阿里的数字安全码
32
+
33
+ 登录过程会提示你填写 验证虚拟MFA设备数字安全码
34
+
35
+ ### 1.如果以前使用过阿里云产品并绑定过
36
+ 可以直接在【阿里云app】或使用【虚拟MFA验证小程序】获取【数字安全码】填写即可,如下:
37
+ - 下载阿里云app
38
+
39
+ ![1.jpg](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_11.jpg)
40
+
41
+ - 定位首页的mfa图标
42
+
43
+ ![2.jpg](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_12.jpg)
44
+
45
+ - 找到对应账号的数字安全码
46
+
47
+ ![3.jpg](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_13.jpg)
48
+
49
+ ### 2.如果以前没有绑定过
50
+ - 如下面选择 虚拟 MFA 设备
51
+
52
+ ![img_5.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_5.png)
53
+
54
+ - 然后在手机安装【阿里云app】或使用【小程序虚拟MFA验证】,然后扫码绑定设备
55
+
56
+ ![img_6.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_6.png)
57
+
58
+ - 在设备添加账号点确定
59
+
60
+ ![img_7.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_17.png)
61
+
62
+ - 之后会每隔一小段时间会刷新获取到最新的【数字安全码】
63
+
64
+ ![img_18.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_18.png)
65
+
66
+ ### 3.如果使用小程序
67
+
68
+ 在微信搜索mfa二次验证如下,具体可以百度下:
69
+
70
+ ![20230501-205150.jpg](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/20230501-205150.jpg)
71
+
72
+ ### 4.设置昵称申请加入
73
+
74
+ 登录成功后会提示你加入团队,设置号昵称点加入
75
+
76
+ ![img_4.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_4.png)
77
+
78
+ ![img_7.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_7.png)
79
+
80
+ ## 查看个人账号信息
81
+
82
+ 等待审核通过后,刷新页面,点击进入企业
83
+
84
+ ![img_8.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_8.png)
85
+
86
+ ### 1.选择角色
87
+
88
+ 选择作为研发者,开始工作
89
+
90
+ ![img_9.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_9.png)
91
+
92
+ ### 2.进入制品仓库
93
+ 在工作台选择【制品仓库】进入
94
+
95
+ ![Snipaste_2023-05-01_21-22-33.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/Snipaste_2023-05-01_21-22-33.png)
96
+
97
+ ### 3.查看账号密码
98
+
99
+ 选择设置查看个人账号信息
100
+
101
+ ![Snipaste_2023-05-01_21-24-27.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/Snipaste_2023-05-01_21-24-27.png)
102
+
103
+ ![Snipaste_2023-05-01_21-26-20.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/Snipaste_2023-05-01_21-26-20.png)
104
+
105
+ ## 设置私库源
106
+
107
+ ### 1.新建rc文件
108
+
109
+ 在需要使用94智能sdk的项目根目录,新建一个rc文件:
110
+
111
+ - 如果你使用的npm或pnpm,新建.npmrc,内容如下:
112
+ ```hell
113
+ @94ai:registry=https://packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/
114
+ //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:username=username
115
+ //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:_password=password
116
+ //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:always-auth=true
117
+ ```
118
+
119
+ - 如果你使用的yarn1,新建.yarnrc,内容如下:
120
+ ```shell
121
+ "@94ai:registry" "https://packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/"
122
+ "//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:username" "username"
123
+ "//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:_password" "password"
124
+ "//packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:always-auth" true
125
+ ```
126
+
127
+ - 如果你使用的yarn2~3,新建.yarnrc.yml,内容如下:
128
+ ```shell
129
+ enableImmutableInstalls: false
130
+ nodeLinker: node-modules
131
+ yarnPath: .yarn/releases/yarn-3.1.1.cjs
132
+ #npmRegistryServer: "https://nexusdev.k8s.94ai.pro/repository/npm-group/"
133
+ npmScopes:
134
+ 94ai:
135
+ npmRegistryServer: "https://packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/"
136
+ npmAlwaysAuth: true
137
+ npmAuthIdent: "username:password"
138
+ #unsafeHttpWhitelist:
139
+ # - 192.168.20.9
140
+ ```
141
+
142
+ ### 2.配置个人账号信息
143
+
144
+ 1. 把对应rc文件`username`替换成你的账号名称
145
+
146
+ 2. 如果你使用的是yarn1,npm或pnpm,则把你的账号密码做base64加密之后替换掉对应rc文件`password`,如在这里[在线base64加密](https://stackoverflow.org.cn/base64/)获取base64加密的密码
147
+
148
+ ![img_10.png](https://xccjhzjh.oss-cn-hongkong.aliyuncs.com/xccjh-images/img_10.png)
149
+
150
+ 3. 如果你使用的是yarn2~3,则把你的账号密码直接替换掉对应rc文件`password`
151
+
152
+ ### 3.安装依赖
153
+
154
+ 之后该项目只要是@94ai域的私包都会经过94智能私服抓取
155
+
156
+ 其他包【包括@94ai域私包的子包(非@94ai域)】都会经由个人电脑配置的npm包registry源抓取
157
+
158
+ 执行install命令即可成功安装@94ai域的所有私包,包括sdk,如
159
+
160
+ ```shell
161
+ $ yarn add @94ai/softphone
162
+ # or
163
+ $ npm i @94ai/softphone
164
+ # or
165
+ $ pnpm add @94ai/softphone
166
+ ```
167
+
168
+ # 使用
169
+ ## 初始化
170
+ ### 1.作为NPM包使用
171
+ ```ts
172
+ // Example:
173
+ // 1.softphone.ts
174
+ import { UserAgent, UserAgentManager, URI } from '@94ai/softphone'
175
+ import FingerprintJS from '@fingerprintjs/fingerprintjs'
176
+ import useToast from '@/utils/useToast'
177
+
178
+ export function createRandomToken(size, base = 32) {
179
+ let token = "";
180
+ for (let i = 0; i < size; i++) {
181
+ const r = Math.floor(Math.random() * base);
182
+ token += r.toString(base);
183
+ }
184
+ return token;
185
+ }
186
+
187
+ // transport层配置
188
+ export interface TransportOptions {
189
+ /** websocket协商地址, server和wsServers 必须二选一*/
190
+ server?: string
191
+ /** 多个地址开启负载均衡模式 */
192
+ wsServers?: string | string[] | {
193
+ /** websocket协商地址 */
194
+ ws_uri: string,
195
+ /** 权重 */
196
+ weight: number
197
+ }[]
198
+ /**
199
+ * websocke初始化连接等待超时时间
200
+ * @default 5
201
+ */
202
+ connectionTimeout?: number
203
+ /**
204
+ * transport层客户端保活最大重连尝试次数
205
+ * @default 3
206
+ */
207
+ maxReconnectionAttempts?: number
208
+ /**
209
+ * transport层客户端保活重连动作执行间隔时间,单位秒,同UserAgentOptions.reconnectionDelay
210
+ * @default 4
211
+ */
212
+ reconnectionTimeout?: number
213
+ /**
214
+ * transport层The time (Number) in seconds to wait in between CLRF keepAlive sequences are sent.
215
+ * @default 0
216
+ */
217
+ keepAliveInterval?: number
218
+ /**
219
+ * transport层The time (Number) in seconds to debounce sending CLRF keepAlive sequences by
220
+ * @default 10
221
+ */
222
+ keepAliveDebounce?: number
223
+ /**
224
+ * transport层If true, messages sent and received by the transport are logged.
225
+ * @default false
226
+ */
227
+ traceSip?: boolean
228
+ }
229
+ // sip层配置
230
+ export interface UserAgentOptions {
231
+ /**
232
+ * 通过openApi获取坐席账号分机密码
233
+ * @default ''
234
+ */
235
+ authorizationPassword: string,
236
+ /**
237
+ * 通过openApi获取坐席账号分机用户名
238
+ * @default ''
239
+ */
240
+ authorizationUsername: string,
241
+ /**
242
+ * 指纹,唯一标志,用来排查线路故障,默认随机指纹,可选
243
+ * @default createRandomToken(12) + ".invalid"
244
+ */
245
+ viaHost?: string,
246
+ /**
247
+ * sip服务地址,必填,需要服务可达的地址
248
+ * @default new URI("sip", "anonymous." + createRandomToken(6), "anonymous.invalid") })
249
+ */
250
+ uri: URI,
251
+ /**
252
+ * sip日志查看等级,一般情况下生产开error,开发用debugger
253
+ * @default 'log'
254
+ */
255
+ logLevel?: 'debug' | 'log' | 'warn' | 'error',
256
+ /**
257
+ * 一般设置同authorizationUsername,相当于MicroSIP的显示名称
258
+ * @default createRandomToken(8)
259
+ */
260
+ contactName?: string
261
+ /**
262
+ * 签入来电后多长时间不执行接听会话自动结束会话,单位秒
263
+ * @default 60
264
+ */
265
+ noAnswerTimeout?: number,
266
+ /**
267
+ * transport层协议配置
268
+ */
269
+ transportOptions: TransportOptions,
270
+ /**
271
+ * sip层 - 重连尝试间隔,为了兼容sipjs,同reconnectionInterval
272
+ * @default 3
273
+ */
274
+ reconnectionDelay?: number,
275
+ /**
276
+ * sip层 - 重连尝试间隔,单位秒
277
+ * @default 3
278
+ */
279
+ reconnectionInterval?: number,
280
+ /**
281
+ * sip层 - 重连失败最大尝试次数
282
+ * @default 100
283
+ */
284
+ reconnectionAttempts?: number,
285
+ /**
286
+ * sip层 - 重连成功后尝试重新注册检间隔,单位秒
287
+ * @default 3
288
+ */
289
+ registerInterval?: number,
290
+ /**
291
+ * sip层 - 重连成功最大尝试注册次数
292
+ * @default 3
293
+ */
294
+ registerAttempts?: number
295
+ /**
296
+ * sip层 - ping动作间隔,单位秒
297
+ * @default 8
298
+ */
299
+ optionsPingInterval?: number
300
+ /**
301
+ * sip层 - ping最大失败尝试次数后开始重连,防止网络抖动引起非必要重连
302
+ * @default 3
303
+ */
304
+ optionsPingAttempts?: number
305
+ /**
306
+ * sip层 - 自定义通讯header
307
+ */
308
+ sipHeaders?: Array<string>;
309
+ }
310
+
311
+ export class SoftphoneManager {
312
+ static #getDeviceId: () => Promise<string>
313
+ #userAgentManager: InstanceType<typeof UserAgentManager>
314
+
315
+ static {
316
+ /**
317
+ * 获取指纹
318
+ */
319
+ SoftphoneManager.#getDeviceId = (() => {
320
+ let visitorId = ''
321
+ return async (): Promise<string> => {
322
+ if (visitorId) return visitorId
323
+ const fp = await FingerprintJS.load()
324
+ const result = await fp.get()
325
+ visitorId = result.visitorId
326
+ return visitorId
327
+ }
328
+ })()
329
+ }
330
+ public async getUserAgentManager = () => {
331
+ if (!this.#userAgentManager) {
332
+ this.#userAgentManager = UserAgentFactory.getUserAgentManager(await this.initSeatsInfo()) // 👈 得到softphone代理对象
333
+ }
334
+ return this.#userAgentManager
335
+ }
336
+ public getUserAgentManagerSync = (config?: UserAgentOptions) => {
337
+ if (!this.#userAgentManager) {
338
+ this.#userAgentManager = UserAgentFactory.getUserAgentManager(config) // 👈 得到softphone代理对象
339
+ }
340
+ return this.#userAgentManager
341
+ }
342
+ /**
343
+ * 通过openApi获取分机信息
344
+ */
345
+ public async initSeatsInfo = (): Promise<UserAgentOptions> => {
346
+ const result = await getSeatsInfo()
347
+ if (result.code === 200) {
348
+ const {
349
+ extPassword, // 分机密码
350
+ extensionNumber, // 分机号
351
+ wsRegisterAddress, // socket地址
352
+ wsProtocol // socket协议
353
+ } = result.data
354
+ return {
355
+ authorizationPassword: extPassword,
356
+ authorizationUsername: extensionNumber,
357
+ viaHost: `${await SoftphoneManager.#getDeviceId()}.sip`,
358
+ uri: UserAgent.makeURI(`sip:${extensionNumber}@${wsRegisterAddress}`),
359
+ logLevel: 'error',
360
+ transportOptions: {
361
+ server: `${wsProtocol}://${wsRegisterAddress}`
362
+ },
363
+ contactName: extensionNumber
364
+ }
365
+ }
366
+ useToast.showToast('获取分机信息失败')
367
+ throw new Error('获取分机信息失败')
368
+ }
369
+ /**
370
+ * 签入
371
+ */
372
+ public connect = async () => {
373
+ try {
374
+ await this.getUserAgentManagerSync().prepareUserAgent(
375
+ /** config */
376
+ {
377
+ /** 当软电话状态变化时会实时刷新这个方法 */
378
+ refresh: (path: keyof typeof userAgentStatusDefault, value: boolean) => {
379
+
380
+ }
381
+ },
382
+ /** event */
383
+ {
384
+ /** 当有外呼过来 */
385
+ onInvite: async (invitation: Invitation) => {
386
+
387
+ }
388
+ })
389
+ } catch (e) {
390
+ const errorInfo = e as Error
391
+ if (errorInfo.message === 'sip register fail with code 503' ||
392
+ errorInfo.message.indexOf('WebSocket closed') > -1 && errorInfo.message.indexOf('code: 1006') > -1) {
393
+ useToast.showToast('软电话服务器异常,签入失败')
394
+ } else {
395
+ useToast.showToast(errorInfo.message)
396
+ }
397
+ throw e
398
+ }
399
+ }
400
+ // ...
401
+ }
402
+ export default new SoftphoneManager()
403
+
404
+ // 2.App.vue
405
+ import commonSoftphone from './softphone.ts'
406
+ onBeforeMount(async () => {
407
+ await commonSoftphone.getUserAgentManager()
408
+ })
409
+
410
+ // 3. softphone.vue
411
+ // <button @click="connect">签入</button>
412
+ import commonSoftphone from './softphone.ts'
413
+ // 签入
414
+ const connect = async () => {
415
+ await commonSoftphone.connect()
416
+ }
417
+ ```
418
+ ### 2.直接在JavaScript中使用
419
+ 1. 获取umd文件
420
+ - 确保安装了node,随手新建一个目录,打开cmd或bash。
421
+ - 设置@4ai域账号授权,依次执行执行如下,其中`:username=xxxx`替换你的账号名字,`:_password=xxxx`替换你的账号密码在做base64加密后的结果
422
+ ```shell
423
+ npm config set @94ai:registry=https://packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/
424
+ npm config set //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:username=xxx
425
+ npm config set //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:_password=xxx
426
+ npm config set //packages.aliyun.com/644f755a97d94d909e43534c/npm/npm-registry/:always-auth=true
427
+ ```
428
+ - 最后执行`npx -y @94ai/softphone -- sdk-umd`,之后在当前执行命令路径下即可获取到`softphone.umd.min.js`
429
+
430
+ 2. 在index.html引入`softphone.umd.min.js`文件
431
+ ```html
432
+ <!DOCTYPE html>
433
+ <html lang="en">
434
+ <head>
435
+ <meta charset="UTF-8" />
436
+ <meta name="viewport"
437
+ content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" />
438
+ <title>softphone</title>
439
+ <style>
440
+ #softphone-app {
441
+ text-align: center;
442
+ padding-top: 50px;
443
+ }
444
+ </style>
445
+ </head>
446
+ <body>
447
+ <div id="softphone-app">
448
+ <button id="connectToServer">连接服务器</button>
449
+ <br>
450
+ <br>
451
+ <button id="disconnect" disabled>断开连接服务器</button>
452
+ <br>
453
+ <br>
454
+ <button id="ignore" disabled>忽略</button>
455
+ <br>
456
+ <br>
457
+ <button id="answer" disabled>接听</button>
458
+ <br>
459
+ <br>
460
+ <button id="hangUp" disabled>挂断</button>
461
+ <br>
462
+ <br>
463
+ <button id="press" disabled>按*转人工</button>
464
+ <br>
465
+ <br>
466
+ <button id="localVoiceAccess" disabled>本地声音接入</button>
467
+ <br>
468
+ <br>
469
+ <button id="remoteSoundAccess" disabled>远程声音接入</button>
470
+ <br>
471
+ <br>
472
+ <button id="localSoundDisconnection" disabled>本地声音断开</button>
473
+ <br>
474
+ <br>
475
+ <button id="remoteSoundDisconnection" disabled>远程声音断开</button>
476
+ <br>
477
+ <br>
478
+ <audio
479
+ id="remoteAudio"
480
+ controls
481
+ style="display:none"
482
+ >
483
+ <p>Your browser doesn't support HTML5 audio - remoteAudio</p>
484
+ </audio>
485
+ <audio
486
+ id="localAudio"
487
+ style="display:none"
488
+ src="./antique_phone.mp3"
489
+ controls
490
+ loop
491
+ >
492
+ <p>Your browser doesn't support HTML5 audio - localAudio</p>
493
+ </audio>
494
+ </div>
495
+ <script src="../lib/softphone.umd.min.js"></script>
496
+ <script src="./fp.umd.min.js"></script>
497
+ <script>
498
+ /**
499
+ * 1.确保你的企业 【坐席外呼依赖】配置项 是【标识】 (默认是【登录】,如果不确定联系94运维在管理后台查看以及配置)
500
+ * 2. 使用 【https://94ai.yuque.com/staff-kqoz0c/xed39g/obnqst?singleDoc# 《SG-九四智能API开放文档(全)》 密码:tvgm】 3.1 获取坐席信息,以及3.2 修改坐席状态接口,保证坐席状态在线
501
+ * 3. 点击连接服务器
502
+ * 4. 在决策外呼一个任务,确保你的坐席账号在任务配置的坐席组里
503
+ * 5. 电话过来后会触发签入注册好的onInvite,这个时候就可以接听,挂断,忽略,转人工,静音等等
504
+ * 6. 《SG-九四智能API开放文档(全)》该文档获取授权调用接口的问题找 航航同学
505
+ */
506
+
507
+ const getDeviceId = (() => {
508
+ let visitorId = ''
509
+ return async () => {
510
+ if (visitorId) return visitorId
511
+ const fp = await FingerprintJS.load()
512
+ const result = await fp.get()
513
+ visitorId = result.visitorId
514
+ return visitorId
515
+ }
516
+ })()
517
+ document.addEventListener('DOMContentLoaded', async (event) => {
518
+ const phoneRing = './antique_phone.mp3' // 👈 铃声自己当以一个音频即可
519
+ const ignore = document.getElementById('ignore')
520
+ const answer = document.getElementById('answer')
521
+ const hangUp = document.getElementById('hangUp')
522
+ const connectToServer = document.getElementById('connectToServer')
523
+ const disconnect = document.getElementById('disconnect')
524
+ const press = document.getElementById('press')
525
+ const localVoiceAccess = document.getElementById('localVoiceAccess')
526
+ const remoteSoundAccess = document.getElementById('remoteSoundAccess')
527
+ const localSoundDisconnection = document.getElementById('localSoundDisconnection')
528
+ const remoteSoundDisconnection = document.getElementById('remoteSoundDisconnection')
529
+ const localAudio = document.getElementById('localAudio')
530
+ const remoteAudio = document.getElementById('remoteAudio')
531
+ const viaHost = `${await getDeviceId()}.sip`
532
+ /** 接口获取开始 */
533
+ /** 通过接口获取 https://94ai.yuque.com/staff-kqoz0c/xed39g/obnqst?singleDoc# 《SG-九四智能API开放文档(全)》 密码:tvgm 👈 不知道怎么调接口找 航航同学 */
534
+ /** 3.1坐席相关接口查询坐席信息 */
535
+ const extensionNumber = '9288'
536
+ const extPassword = 'zjh13542240708'
537
+ const wsProtocol = 'wss'
538
+ const wsRegisterAddress = 's28.94ai.com:7443'
539
+ const sipServerHost = `sip:${extensionNumber}@${wsRegisterAddress}`
540
+ const wsAddress = `${wsProtocol}://${wsRegisterAddress}`
541
+ /** 3.2修改坐席账号状态 */
542
+ const request = {
543
+ post(url) {
544
+ console.log('写你的调用的openapi改坐席状态,要写好它')
545
+ }
546
+ }
547
+ const editSeats = async (params) => {
548
+ return request.post('/seats/editLineStatus')
549
+ }
550
+ await editSeats({ lineStatus: 1 })
551
+ /** 接口获取结束 */
552
+
553
+ const {
554
+ UserAgentFactory,
555
+ UserAgent,
556
+ playMedia,
557
+ pauseMedia,
558
+ SessionState,
559
+ getMedia
560
+ } = softphone // 👈 sdk
561
+
562
+ /**
563
+ * 开启电话响铃
564
+ */
565
+ const phoneRings = () => {
566
+ remoteAudio.src = phoneRing
567
+ playMedia('localAudio')
568
+ }
569
+
570
+ /**
571
+ * 关闭电话响铃
572
+ */
573
+ const stopPhoneRings = () => {
574
+ remoteAudio.src = ''
575
+ pauseMedia('localAudio')
576
+ }
577
+
578
+ /**
579
+ * 获取软电话代理实例
580
+ */
581
+ const userAgentManager = UserAgentFactory.getUserAgentManager({
582
+ authorizationPassword: extPassword,
583
+ authorizationUsername: extensionNumber,
584
+ viaHost,
585
+ uri: UserAgent.makeURI(sipServerHost),
586
+ logLevel: 'error',
587
+ transportOptions: {
588
+ server: wsAddress
589
+ },
590
+ contactName: extensionNumber
591
+ })
592
+
593
+ /**
594
+ * 签入
595
+ */
596
+ const connect = () => {
597
+ userAgentManager.prepareUserAgent(
598
+ { // config
599
+ refresh (path, value) { // 当软电话状态变化时会实时刷新这个方法
600
+ console.log(path, value)
601
+ }
602
+ },
603
+ { // event
604
+ onInvite (invitation) { // 当有外呼过来
605
+ syncSipMessage(invitation.incomingInviteRequest.message.headers)
606
+ phoneRings() // 播放模拟来电响铃
607
+ ignore.disabled = false
608
+ answer.disabled = false
609
+ localSoundDisconnection.disabled = false
610
+ remoteSoundDisconnection.disabled = false
611
+ invitation.stateChange.addListener((state) => { // 一旦接听(执行accept),监听会话的生命周期
612
+ switch (state) {
613
+ case SessionState.Initial:
614
+ break
615
+ case SessionState.Establishing:
616
+ break
617
+ case SessionState.Established: // session建立后就可拿到webrtc的各种基础api,如userAgentManager.getPeerConnection(),如userAgentManager.getSenders()等等
618
+ const mediaElement1 = getMedia('remoteAudio')
619
+ mediaElement1.srcObject = userAgentManager.getStream() // 获取流
620
+ mediaElement1.play() // 把softphone流导入到audio接入用户语音
621
+ hangUp.disabled = false
622
+ break
623
+ case SessionState.Terminating:
624
+ case SessionState.Terminated: // 在挂断电话时候会执行
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
+ const mediaElement2 = getMedia('remoteAudio') // 获取audio dom
633
+ mediaElement2.srcObject = null
634
+ mediaElement2.pause() // 释放audio
635
+ stopPhoneRings()
636
+ break
637
+ default:
638
+ throw new Error('Unknown session state.')
639
+ }
640
+ })
641
+ }
642
+ }
643
+ )
644
+ disconnect.disabled = false
645
+ connectToServer.disabled = true
646
+ ignore.disabled = true
647
+ answer.disabled = true
648
+ hangUp.disabled = true
649
+ localSoundDisconnection.disabled = true
650
+ remoteSoundDisconnection.disabled = true
651
+ localVoiceAccess.disabled = true
652
+ remoteSoundAccess.disabled = true
653
+ }
654
+
655
+ /**
656
+ * 签出
657
+ */
658
+ const disConnect = () => {
659
+ userAgentManager.dispose() // 👈 一个方法安全销毁
660
+ connectToServer.disabled = false
661
+ disconnect.disabled = true
662
+ ignore.disabled = true
663
+ answer.disabled = true
664
+ hangUp.disabled = true
665
+ localSoundDisconnection.disabled = true
666
+ remoteSoundDisconnection.disabled = true
667
+ localVoiceAccess.disabled = true
668
+ remoteSoundAccess.disabled = true
669
+ }
670
+
671
+ /**
672
+ * 先监听后接听
673
+ */
674
+ let monitorFirstAnswerAfter = true // 先监听任务需要 按*号转人工 才可以让用户听到坐席声音
675
+ /**
676
+ * sip协议通讯
677
+ */
678
+ const syncSipMessage = (headers) => {
679
+ monitorFirstAnswerAfter = !(String(headers['X-Aftertransferlabour'] && headers['X-Aftertransferlabour'][0].raw) === '1')
680
+ }
681
+
682
+ /**
683
+ * 按*号转人工
684
+ */
685
+
686
+ const sendStarDtmf = () => {
687
+ if (monitorFirstAnswerAfter) {
688
+ userAgentManager.sendStarDtmf()
689
+ press.disabled = true
690
+ }
691
+ }
692
+
693
+ /**
694
+ * 挂断
695
+ */
696
+ const hangUpInvite = () => {
697
+ stopPhoneRings() // 暂停来电铃声
698
+ userAgentManager.hangUpInvite() // 👈
699
+ ignore.disabled = true
700
+ answer.disabled = true
701
+ hangUp.disabled = true
702
+ localSoundDisconnection.disabled = true
703
+ remoteSoundDisconnection.disabled = true
704
+ localVoiceAccess.disabled = true
705
+ remoteSoundAccess.disabled = true
706
+ }
707
+ /**
708
+ * 忽略
709
+ */
710
+ const ignoreInvite = () => {
711
+ stopPhoneRings() // 暂停来电铃声
712
+ userAgentManager.ignoreInvite()
713
+ ignore.disabled = true
714
+ answer.disabled = true
715
+ hangUp.disabled = true
716
+ localSoundDisconnection.disabled = true
717
+ remoteSoundDisconnection.disabled = true
718
+ localVoiceAccess.disabled = true
719
+ remoteSoundAccess.disabled = true
720
+ }
721
+ /**
722
+ * 接听
723
+ */
724
+ const acceptInvite = () => {
725
+ stopPhoneRings() // 暂停来电铃声
726
+ userAgentManager.acceptInvite()
727
+ if (monitorFirstAnswerAfter) {
728
+ press.disabled = false
729
+ }
730
+ hangUp.disabled = false
731
+ answer.disabled = true
732
+ ignore.disabled = true
733
+ }
734
+
735
+ /**
736
+ * 传输本地声音到远端
737
+ */
738
+ const unMuteLocalAudio = () => {
739
+ localVoiceAccess.disabled = true
740
+ localSoundDisconnection.disabled = false
741
+ userAgentManager.unMuteLocalAudio()
742
+ }
743
+ /**
744
+ * 不传输本地声音到远端
745
+ */
746
+ const muteLocalAudio = () => {
747
+ remoteSoundAccess.disabled = true
748
+ remoteSoundDisconnection.disabled = false
749
+ userAgentManager.muteLocalAudio()
750
+ }
751
+ /**
752
+ * 接听远端声音
753
+ */
754
+ const unMuteRemoteAudio = () => {
755
+ localSoundDisconnection.disabled = true
756
+ localVoiceAccess.disabled = false
757
+ userAgentManager.unMuteRemoteAudio()
758
+ }
759
+ /**
760
+ * 不接听远端声音
761
+ */
762
+ const muteRemoteAudio = () => {
763
+ remoteSoundDisconnection.disabled = true
764
+ remoteSoundAccess.disabled = false
765
+ userAgentManager.muteRemoteAudio()
766
+ }
767
+
768
+ /** 注册事件开始 */
769
+ ignore.addEventListener('click', ignoreInvite)
770
+ answer.addEventListener('click', acceptInvite)
771
+ hangUp.addEventListener('click', hangUpInvite)
772
+ connectToServer.addEventListener('click', connect)
773
+ disconnect.addEventListener('click', disConnect)
774
+ press.addEventListener('click', sendStarDtmf)
775
+ localVoiceAccess.addEventListener('click', unMuteLocalAudio)
776
+ remoteSoundAccess.addEventListener('click', muteLocalAudio)
777
+ localSoundDisconnection.addEventListener('click', unMuteRemoteAudio)
778
+ remoteSoundDisconnection.addEventListener('click', muteRemoteAudio)
779
+ /** 注册事件结束 */
780
+ })
781
+
782
+
783
+ </script>
784
+ </body>
785
+ </html>
786
+ ```
787
+ [](https://cdn.nlark.com/yuque/0/2023/gif/548727/1684390483858-5531b7e1-8f1d-4319-b18f-dcc123dceae6.gif)
788
+
789
+ > 1. tip 获取fingerprintjs: https://unpkg.com/@fingerprintjs/fingerprintjs@3.4.1/dist/fp.umd.min.js
790
+ > 2. const phoneRing = './antique_phone.mp3' // 👈 铃声自己当以一个音频即可
791
+ > 3. ￳直接双击打开的html的demo打电话这个模拟不了线上域名部署环境可能出现的问题,可以实现看各种接听挂断签入转人工等效果,file协议和localhost默认在浏览器安全域名内。注意线上环境浏览器是有限制要https协议的,http是使用不了浏览器webrtc的api的,要配置下浏览器安全上下文域名白名单
792
+
793
+ ## 签入
794
+ ```js
795
+ import { playMedia, getMedia, SessionState } from '@94ai/softphone'
796
+ import { ref } from 'vue'
797
+ import popNotice from './usePopNotice.ts' // 通知工具,见下面demo说明
798
+
799
+ const userAgentStatus = ref({ // 视图层响应式
800
+ connectStatus: false, // 软电话是否已签入
801
+ registerStatus: false, // 软电话是否已注册
802
+ invitatingStatus: false, // 软电话是否正拨出
803
+ incomingStatus: false, // 软电话是否正来电
804
+ answerStatus: false, // 软电话是否正接听
805
+ reconnectStatus: false, // 软电话是否正重连
806
+ })
807
+
808
+ userAgentManager.prepareUserAgent(
809
+ { // config
810
+ refresh(path, value) { // 当软电话状态变化时会实时刷新这个方法
811
+ userAgentStatus[path] = value // 外部想要响应状态可以实时更新外部的userAgentStatus
812
+ }
813
+ },
814
+ { // event
815
+ onInvite(invitation) { // 当有外呼过来
816
+ playMedia('localAudio') // 播放模拟来电响铃
817
+ popNotice.popNotification('来电了') // 提示通知 来电了
818
+ invitation.stateChange.addListener((state) => { // 一旦接听(执行accept),监听会话的生命周期
819
+ switch (state) {
820
+ case SessionState.Initial:
821
+ break
822
+ case SessionState.Establishing:
823
+ break
824
+ case SessionState.Established: // session建立后就可拿到webrtc的各种基础api,如userAgentManager.getPeerConnection(),如userAgentManager.getSenders()等等
825
+ const mediaElement1 = getMedia('remoteAudio')
826
+ mediaElement1.srcObject = userAgentManager.getStream() // 获取流
827
+ mediaElement1.play() // 把softphone流导入到audio接入用户语音
828
+ break
829
+ case SessionState.Terminating:
830
+ case SessionState.Terminated: // 在挂断电话时候会执行
831
+ const mediaElement2 = getMedia('remoteAudio') // 获取audio dom
832
+ mediaElement2.srcObject = null
833
+ mediaElement2.pause() // 释放audio
834
+ break
835
+ default:
836
+ throw new Error('Unknown session state.')
837
+ }
838
+ })
839
+ }
840
+ })
841
+ ```
842
+ ## 接听
843
+ ```js
844
+ import { refreshShowTime, pauseMedia } from '@94ai/softphone'
845
+
846
+ const acceptInvite = () => {
847
+ refreshShowTime() // 重新计算通话时长(非必须)
848
+ pauseMedia('localAudio') // 暂停来电铃声(非必须)
849
+ userAgentManager.acceptInvite() // 👈 接入电话流
850
+ }
851
+ ```
852
+ ## 忽略
853
+ ```js
854
+ import { pauseMedia } from '@94ai/softphone'
855
+
856
+ const ignoreInvite = () => {
857
+ pauseMedia('localAudio') // 暂停来电铃声(非必须)
858
+ userAgentManager.ignoreInvite() // 👈 忽略
859
+ }
860
+ ```
861
+ ## 挂断
862
+ ```js
863
+ import { refreshShowTime } from '@94ai/softphone'
864
+
865
+ const hangUpInvite = () => {
866
+ refreshShowTime() // 重新计算通话时长(非必须)
867
+ userAgentManager.hangUpInvite() // 👈 挂断
868
+ }
869
+ ```
870
+ ## 按*号转人工
871
+ ```js
872
+ import { refreshShowTime } from '@94ai/softphone'
873
+
874
+ const sendStarDtmf = () => {
875
+ refreshShowTime() // 重新计算通话时长(非必须)
876
+ userAgentManager.sendStarDtmf() // 👈 按*号转人工
877
+ }
878
+ ```
879
+
880
+ ## 销毁
881
+ ```js
882
+ import { refreshShowTime } from '@94ai/softphone'
883
+
884
+ const disconnect = () => {
885
+ refreshShowTime() // 重新计算通话时长(非必须)
886
+ userAgentManager.dispose() // 👈 一个方法安全销毁
887
+ }
888
+ ```
889
+ ## 传输本地声音到远端
890
+ ```js
891
+ const unMuteLocalAudio = () => {
892
+ userAgentManager.unMuteLocalAudio() // 👈 通过webrtc的方式控制 本地音频流 推送到 远端
893
+ }
894
+ ```
895
+ ## 不传输本地声音到远端
896
+ ```js
897
+ const muteLocalAudio = () => {
898
+ userAgentManager.muteLocalAudio() // 👈 通过webrtc的方式控制 本地音频流 禁用推送
899
+ }
900
+ ```
901
+ ## 接听远端声音
902
+ ```js
903
+ const unMuteRemoteAudio = () => {
904
+ userAgentManager.unMuteRemoteAudio() // 👈 通过webrtc的方式控制 是否接受 远端音频流
905
+ }
906
+ ```
907
+ ## 不接听远端声音
908
+ ```js
909
+ const muteRemoteAudio = () => {
910
+ userAgentManager.muteRemoteAudio() // 👈 通过webrtc的方式控制 是否接受 远端音频流
911
+ }
912
+ ```
913
+ ## 获取webrtc基础api
914
+ ```js
915
+ const senders = userAgentManager.getSenders()
916
+ const receivers = userAgentManager.getReceivers()
917
+ const peerConnection = userAgentManager.getPeerConnection()
918
+ ```
919
+ ## 获取代理用户相关sip协议通讯实例
920
+ ```js
921
+ const userAgent = userAgentManager.getUserAgent()
922
+ const sessionDescriptionHandler = userAgentManager.getSessionDescriptionHandler()
923
+ const currentInvitation = userAgentManager.getCurrentInvitation()
924
+ const currentInviter = userAgentManager.getCurrentInviter()
925
+ const localMixedMediaStream = userAgentManager.getStream()
926
+ ```
927
+ ## 查看代理用户目前状态
928
+ ```ts
929
+ interface userAgentStatus {
930
+ connectStatus: boolean, // 软电话是否已签入
931
+ registerStatus: boolean, // 软电话是否已注册
932
+ invitatingStatus: boolean, // 软电话是否正拨出
933
+ incomingStatus: boolean, // 软电话是否正来电
934
+ answerStatus: boolean, // 软电话是否正接听
935
+ reconnectStatus: boolean, // 软电话是否正在重连,网络断掉,服务器重启,宕机等引起服务不可达时软电话代理会尝试重新连接并签入。这个时候用户拨出等动作可以通过此状态做拦截,通知用户软电话服务器可能正重启或断网等导致服务不可用
936
+ }
937
+ const userAgentStatus: userAgentStatus = userAgentManager.getUserAgentStatue()
938
+ ```
939
+
940
+ > 软电话状态可以根据企业特定业务流程定制扩展额外的状态,如94智能决策系统 额外扩展了 【监听中】,【整理中】,【小休中】等状态
941
+