@94ai/softphone 5.0.9 → 5.0.11

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.
@@ -0,0 +1,667 @@
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>softphone</title>
8
+ <style>
9
+ .nf-transparent {
10
+ visibility: hidden;
11
+ }
12
+ .nf-softphone-text {
13
+ text-align: center;
14
+ line-height: 32px;
15
+ margin-top: -32px;
16
+ }
17
+ .nf-softphone-container {
18
+ margin-left: 10px;
19
+ height: 32px;
20
+ min-width: 600px;
21
+ }
22
+ .nf-softphone-iframe-container {
23
+ height: 32px;
24
+ width: 100%;
25
+ overflow: visible;
26
+ }
27
+ .nf-softphone-iframe {
28
+ width: 100vw;
29
+ height: 100vh;
30
+ top: 0;
31
+ left: 0;
32
+ }
33
+ </style>
34
+ </head>
35
+ <body>
36
+ <div id='softpone-context1' style='overflow: hidden'>
37
+ <div style="display: flex;height: 32px;">
38
+ <div style="min-width: 600px;width: 600px;" id='softphone1'></div>
39
+ </div>
40
+ </div>
41
+ <script>
42
+ function generateUUID() { // Public Domain/MIT
43
+ let d = Date.now();
44
+ if (typeof performance !== 'undefined' && typeof performance.now === 'function'){
45
+ d += performance.now(); //use high-precision timer if available
46
+ }
47
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
48
+ const r = (d + Math.random() * 16) % 16 | 0;
49
+ d = Math.floor(d / 16);
50
+ return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
51
+ });
52
+ }
53
+
54
+ function debounce(func, wait, immediate) {
55
+ let timeout, args, context, timestamp, result
56
+
57
+ const later = function() {
58
+ // 据上一次触发时间间隔
59
+ const last = +new Date() - timestamp
60
+
61
+ // 上次被包装函数被调用时间间隔last小于设定时间间隔wait
62
+ if (last < wait && last > 0) {
63
+ timeout = setTimeout(later, wait - last)
64
+ } else {
65
+ timeout = null
66
+ // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
67
+ if (!immediate) {
68
+ result = func.apply(context, args)
69
+ if (!timeout) context = args = null
70
+ }
71
+ }
72
+ }
73
+
74
+ return function(...args) {
75
+ // @ts-ignore
76
+ context = this
77
+ timestamp = +new Date()
78
+ const callNow = immediate && !timeout
79
+ // 如果延时不存在,重新设定延时
80
+ if (!timeout) timeout = setTimeout(later, wait)
81
+ if (callNow) {
82
+ result = func.apply(context, args)
83
+ // @ts-ignore
84
+ context = args = null
85
+ }
86
+
87
+ return result
88
+ }
89
+ }
90
+
91
+ // 用于存储传入 nextTick 的回调函数
92
+ const callbacks = [];
93
+ // 标记是否已经安排了回调函数的执行
94
+ let pending = false;
95
+
96
+ // 执行队列中的所有回调函数
97
+ function flushCallbacks() {
98
+ pending = false;
99
+ // 复制一份当前的回调队列,避免在执行过程中添加新的回调影响循环
100
+ const copies = callbacks.slice(0);
101
+ callbacks.length = 0;
102
+ // 依次执行每个回调函数,并处理可能出现的异常
103
+ for (let i = 0; i < copies.length; i++) {
104
+ try {
105
+ copies[i]();
106
+ } catch (e) {
107
+ console.error('Error in nextTick callback:', e);
108
+ }
109
+ }
110
+ }
111
+
112
+ // 根据浏览器支持情况选择合适的异步执行方式
113
+ let timerFunc;
114
+ if (typeof Promise !== 'undefined') {
115
+ // 如果支持 Promise,使用 Promise 的 then 方法创建微任务
116
+ const p = Promise.resolve();
117
+ timerFunc = () => {
118
+ p.then(flushCallbacks);
119
+ };
120
+ } else if (typeof MutationObserver !== 'undefined') {
121
+ // 如果支持 MutationObserver,使用它创建微任务
122
+ let counter = 1;
123
+ const observer = new MutationObserver(flushCallbacks);
124
+ const textNode = document.createTextNode(String(counter));
125
+ observer.observe(textNode, {
126
+ characterData: true
127
+ });
128
+ timerFunc = () => {
129
+ counter = (counter + 1) % 2;
130
+ textNode.data = String(counter);
131
+ };
132
+ } else if (typeof setImmediate !== 'undefined') {
133
+ // 如果支持 setImmediate,使用它创建宏任务
134
+ timerFunc = () => {
135
+ setImmediate(flushCallbacks);
136
+ };
137
+ } else {
138
+ // 最后使用 setTimeout 作为兜底方案创建宏任务
139
+ timerFunc = () => {
140
+ setTimeout(flushCallbacks, 0);
141
+ };
142
+ }
143
+
144
+ // 实现 nextTick 函数,支持传入回调函数或返回 Promise
145
+ async function nextTick(cb) {
146
+ return new Promise((resolve) => {
147
+ // 将回调函数和 resolve 函数封装成一个新的回调
148
+ callbacks.push(() => {
149
+ if (cb) {
150
+ try {
151
+ cb();
152
+ } catch (e) {
153
+ console.error('Error in nextTick callback:', e);
154
+ }
155
+ }
156
+ // 当回调执行完毕后,resolve Promise
157
+ resolve(true);
158
+ });
159
+ // 如果当前没有正在执行的回调安排,安排一次新的执行
160
+ if (!pending) {
161
+ pending = true;
162
+ timerFunc();
163
+ }
164
+ });
165
+ }
166
+
167
+ class SoftphoneManager {
168
+
169
+ id
170
+ timer
171
+
172
+ constructor() {
173
+ /**
174
+ * 隔离链接实例
175
+ */
176
+ this.id = generateUUID()
177
+ }
178
+
179
+ option = {
180
+ origin: 'ai-softphone',
181
+ ancestorOrigin: 'nf-softphone',
182
+ destinationOrigin: '*',
183
+
184
+ scrollview: undefined,
185
+ scrollingText: '正在滚动中...',
186
+
187
+ envMap: {
188
+ test: 'http://softphone.test.k8s.com',
189
+ dev: 'http://softphone.dev.k8s.com',
190
+ sg: 'https://seatsg.94ai.com',
191
+ gray: 'http://softphone.gray.94ai.com',
192
+ prod: 'https://seat.94ai.com',
193
+ },
194
+ env: 'prod',
195
+ selector: undefined,
196
+ el: undefined,
197
+
198
+ agentId: undefined,
199
+ agentTag: undefined,
200
+ appKey: undefined,
201
+ appSecret: undefined,
202
+
203
+ sgGateway: undefined,
204
+ sgBase: undefined,
205
+ sgOpen: undefined,
206
+ sgDomain: undefined,
207
+
208
+ timestamp: undefined,
209
+ sign: undefined,
210
+
211
+ extStatus: '0',
212
+
213
+ callShow: '1',
214
+ transferToLaborShow: '1',
215
+ restShow: '1',
216
+ networkDetectShow: '1',
217
+ muteShow: '1',
218
+ logShow: '1',
219
+ timingShow: '1',
220
+ signShow: '1',
221
+
222
+ instanceMap: {},
223
+
224
+ softphoneConnectCallBack: () => {console.log('softphone-connect')},
225
+ softphoneCallRefreshCallBack: () => {console.log('softphone-call-refresh')},
226
+ softphoneSeatStatusChangeCallBack: () => {console.log('softphone-seats-status-change')},
227
+ softphoneAcceptCallBack: () => {console.log('softphone-accept')},
228
+ softphoneIgnoreCallBack: () => {console.log('softphone-ignore')},
229
+ softphoneHangupCallBack: () => {console.log('softphone-hangup')},
230
+ softphoneSessionStateChangeCallBack: () => {console.log('softphone-session-state-change')},
231
+ softphoneIncomingCallBack: () => {console.log('softphone-incoming')},
232
+ softphoneSendDtmfCallBack: () => {console.log('softphone-send-dtmf')},
233
+ softphoneConnectRegisteredCallBack: () => {console.log('softphone-connect-registered')},
234
+ softphoneSignOverduedCallBack: (done) => {
235
+ done({
236
+ timestamp: undefined,
237
+ sign: undefined
238
+ })
239
+ },
240
+ }
241
+
242
+
243
+ loadCssCode = (code) => {
244
+ const styleId = 'nf-softphone'
245
+ if (!document.getElementById(styleId)) {
246
+ const style = document.createElement('style');
247
+ style.id = styleId
248
+ style.type = 'text/css';
249
+ // @ts-ignore
250
+ style.rel = 'stylesheet';
251
+ //for Chrome Firefox Opera Safari
252
+ style.appendChild(document.createTextNode(code));
253
+ //for IE
254
+ //style.styleSheet.cssText = code;
255
+ const head = document.getElementsByTagName('head')[0];
256
+ head.appendChild(style);
257
+ }
258
+ }
259
+
260
+ removeCssCode = () => {
261
+ const styleId = 'nf-softphone'
262
+ const css = document.getElementById(styleId)
263
+ if (css) {
264
+ if (css.parentNode) {
265
+ css.parentNode.removeChild(css);
266
+ }
267
+ }
268
+ }
269
+
270
+ /**
271
+ * 隔离软电话容器
272
+ */
273
+ getContextDomElement = (el) => {
274
+ return (this.option.selector ? (document.querySelector(this.option.selector) ?? document) : document).querySelector(el)
275
+ }
276
+ /**
277
+ * 隔离软电话容器内容
278
+ */
279
+ getSoftphoneDomElement = (el) => {
280
+ return this.getContextDomElement(this.option.el).querySelector(el) || this.getContextDomElement(el) || document.querySelector(el)
281
+ }
282
+
283
+ signOverdued = (data) => {
284
+ const iframe = this.getSoftphoneDomElement('.nf-softphone-iframe')
285
+ iframe?.contentWindow?.postMessage(JSON.stringify({
286
+ origin: 'nf-softphone',
287
+ ancestorOrigin: this.option.ancestorOrigin,
288
+ destinationOrigin: 'ai-softphone',
289
+ type: 'softphone-sign-overdued',
290
+ data
291
+ }), '*')
292
+ }
293
+
294
+ handlingCommunication = (e) => {
295
+ const iframe = this.getSoftphoneDomElement('.nf-softphone-iframe')
296
+ if (iframe) {
297
+ let data = {}
298
+ try {
299
+ data = JSON.parse(e.data)
300
+ } catch (e) {}
301
+ if ((data.ancestorOrigin === this.option.ancestorOrigin && data.origin !== this.option.ancestorOrigin) ||
302
+ (data.ancestorOrigin === this.option.origin && data.origin === this.option.origin)) {
303
+ if (data.destinationOrigin === '*' || data.destinationOrigin === this.option.destinationOrigin) {
304
+ if (data.type === 'softphone-expand') {
305
+ if (data.data === 'expand') {
306
+ iframe.setAttribute('style', 'position: fixed;z-index:999;');
307
+ } else if (data.data === 'shrink') {
308
+ iframe.removeAttribute('style');
309
+ }
310
+ return
311
+ }
312
+ if (data.type === 'softphone-connect') {
313
+ this.option.softphoneConnectCallBack?.(data)
314
+ } else if (data.type === 'softphone-call-refresh') {
315
+ this.option.softphoneCallRefreshCallBack?.(data)
316
+ } else if (data.type === 'softphone-seats-status-change') {
317
+ this.option.softphoneSeatStatusChangeCallBack?.(data)
318
+ } else if (data.type === 'softphone-accept') {
319
+ this.option.softphoneAcceptCallBack?.(data)
320
+ } else if (data.type === 'softphone-ignore') {
321
+ this.option.softphoneIgnoreCallBack?.(data)
322
+ } else if (data.type === 'softphone-hangup') {
323
+ this.option.softphoneHangupCallBack?.(data)
324
+ } else if (data.type === 'softphone-session-state-change') {
325
+ this.option.softphoneSessionStateChangeCallBack?.(data)
326
+ } else if (data.type === 'softphone-incoming') {
327
+ this.option.softphoneIncomingCallBack?.(data)
328
+ } else if (data.type === 'softphone-send-dtmf') {
329
+ this.option.softphoneSendDtmfCallBack?.(data)
330
+ } else if (data.type === 'softphone-connect-registered') {
331
+ this.option.softphoneConnectRegisteredCallBack?.(data)
332
+ } else if (data.type === 'softphone-sign-overdued') {
333
+ const done = (data) => {
334
+ const {
335
+ sign,
336
+ timestamp
337
+ } = data
338
+ this.signOverdued({
339
+ sign,
340
+ timestamp
341
+ })
342
+ }
343
+ this.option.softphoneSignOverduedCallBack?.(done)
344
+ }
345
+ }
346
+ }
347
+ }
348
+ }
349
+
350
+ handlingScroll = debounce(async () => {
351
+ const {
352
+ top,
353
+ left,
354
+ } = (await this.refreshStyle() || {})
355
+ if (top && left) {
356
+ (this.getSoftphoneDomElement('.nf-softphone-iframe')).contentWindow.postMessage(JSON.stringify({
357
+ type: 'softphone-scroll',
358
+ origin: this.option.origin,
359
+ ancestorOrigin: this.option.ancestorOrigin,
360
+ destinationOrigin: this.option.destinationOrigin,
361
+ data: {
362
+ paddingTop: top + 'px',
363
+ }
364
+ }), '*')
365
+ }
366
+ }, 500, false)
367
+
368
+ handlerInteractive = () => {
369
+ /**
370
+ * 实时处理层级
371
+ */
372
+ window.addEventListener('message', this.handlingCommunication, false);
373
+ if (this.option.scrollview) {
374
+ document.querySelector(this.option.scrollview)?.addEventListener('scroll', this.handlingScroll)
375
+ document.querySelector(this.option.scrollview)?.addEventListener('scroll', this.handlingScrollTransparent)
376
+ }
377
+
378
+ this.loadCssCode(`
379
+ .nf-transparent {
380
+ visibility: hidden;
381
+ }
382
+ .nf-softphone-text {
383
+ text-align: center;
384
+ line-height: 32px;
385
+ margin-top: -32px;
386
+ }
387
+ .nf-softphone-container {
388
+ margin-left: 10px;
389
+ height: 32px;
390
+ min-width: 600px;
391
+ }
392
+ .nf-softphone-iframe-container {
393
+ height: 32px;
394
+ width: 100%;
395
+ overflow: visible;
396
+ }
397
+ .nf-softphone-iframe {
398
+ width: 100vw;
399
+ height: 100vh;
400
+ top: 0;
401
+ left: 0;
402
+ }
403
+ `);
404
+ }
405
+
406
+ handlerDomRemove = () => {
407
+ Object.keys(this.option.instanceMap).forEach(key => {
408
+ const el = document.getElementById(key)
409
+ if (el && el.parentNode) {
410
+ el.parentNode.removeChild(el)
411
+ }
412
+ })
413
+ }
414
+
415
+ _containerScrolling
416
+
417
+ get containerScrolling() {
418
+ return this._containerScrolling
419
+ }
420
+
421
+ set containerScrolling(value) {
422
+ if (this.containerScrolling !== value) {
423
+ this._containerScrolling = value
424
+ const nfSoftphoneContainer = this.getSoftphoneDomElement('.nf-softphone-container')
425
+ const nfSoftphoneText = this.getSoftphoneDomElement('.nf-softphone-text')
426
+ nfSoftphoneContainer?.setAttribute('class', `nf-softphone-container ${value === true ? 'nf-transparent' : ''}`)
427
+ nfSoftphoneText?.setAttribute('style', `display: ${value === true ? 'block' : 'none'}`)
428
+ }
429
+ }
430
+
431
+ handlingScrollTransparent = () => {
432
+ this.containerScrolling = true
433
+ }
434
+
435
+ refreshStyle = async () => {
436
+ clearTimeout(this.timer)
437
+ this.timer = undefined
438
+ const nfSoftphoneContainer = this.getSoftphoneDomElement('.nf-softphone-container')
439
+ const nfSoftphoneIframeContainer = this.getSoftphoneDomElement('.nf-softphone-iframe-container')
440
+ if (nfSoftphoneContainer && nfSoftphoneIframeContainer) {
441
+ const containerStyle = nfSoftphoneContainer.getAttribute('style')
442
+ const iframeContainerStyle = nfSoftphoneIframeContainer.getAttribute('style')
443
+ nfSoftphoneContainer.removeAttribute('style')
444
+ nfSoftphoneIframeContainer.removeAttribute('style')
445
+ const leftMargin = getComputedStyle(nfSoftphoneContainer).marginLeft
446
+ const top = nfSoftphoneContainer.getBoundingClientRect().top
447
+ if (top <= 0) {
448
+ nfSoftphoneContainer.setAttribute('style', containerStyle)
449
+ nfSoftphoneIframeContainer.setAttribute('style', iframeContainerStyle)
450
+ return {
451
+ top: 0,
452
+ left: 0,
453
+ leftMargin
454
+ }
455
+ }
456
+ const left = nfSoftphoneContainer.getBoundingClientRect().left
457
+ if (left <= 0) {
458
+ nfSoftphoneContainer.setAttribute('style', containerStyle)
459
+ nfSoftphoneIframeContainer.setAttribute('style', iframeContainerStyle)
460
+ return {
461
+ top: 0,
462
+ left: 0,
463
+ leftMargin
464
+ }
465
+ }
466
+ nfSoftphoneContainer.setAttribute('style', `margin-top:${top}px;margin-left:${leftMargin};margin-bottom:${top}px;`)
467
+ nfSoftphoneIframeContainer.setAttribute('style', `margin-top:-${2 * top}px;margin-left:-${left}px;`)
468
+ this.timer = setTimeout(() => {
469
+ clearTimeout(this.timer)
470
+ this.timer = undefined
471
+ this.containerScrolling = false
472
+ }, 100)
473
+ return {
474
+ leftMargin,
475
+ top,
476
+ left
477
+ }
478
+ }
479
+ return {
480
+ leftMargin: 0,
481
+ top: 0,
482
+ left: 0
483
+ }
484
+ }
485
+
486
+ handlerDomGenerater = async () => {
487
+ const nfSoftphone = document.createElement('div');
488
+ const nfSoftphoneText = document.createElement('div');
489
+ nfSoftphoneText.innerText = this.option.scrollingText || '正在滚动中...'
490
+
491
+ const nfSoftphoneContainer = document.createElement('div');
492
+ const iframeContainer = document.createElement('div');
493
+ const iframe = document.createElement('iframe');
494
+
495
+ const uuid = this.id + '-container'
496
+ this.option.instanceMap[uuid] = nfSoftphone
497
+ nfSoftphone.setAttribute('id', uuid)
498
+ nfSoftphone.setAttribute('class', 'nf-softphone')
499
+ nfSoftphoneText.setAttribute('class', 'nf-softphone-text')
500
+ nfSoftphoneText.setAttribute('style', "display: none;")
501
+ nfSoftphoneContainer.setAttribute('class', "nf-softphone-container")
502
+ iframeContainer.setAttribute('class', "nf-softphone-iframe-container")
503
+ iframe.setAttribute('class', "nf-softphone-iframe")
504
+ iframe.setAttribute('src', "")
505
+ iframe.setAttribute('frameborder', "0")
506
+ iframe.setAttribute('scrolling', "no")
507
+ iframe.setAttribute('allow', "camera *; microphone *")
508
+ iframeContainer.appendChild(iframe)
509
+ nfSoftphoneContainer.appendChild(iframeContainer)
510
+ nfSoftphone.appendChild(nfSoftphoneContainer)
511
+ nfSoftphone.appendChild(nfSoftphoneText)
512
+ const destination = (this.option.el ? this.getSoftphoneDomElement(this.option.el) ?? document.body : document.body)
513
+ destination.appendChild(nfSoftphone)
514
+ await nextTick()
515
+ const {
516
+ top,
517
+ left,
518
+ } = await this.refreshStyle()
519
+ const {
520
+ envMap,
521
+ env,
522
+ agentTag,
523
+ agentId,
524
+ appSecret,
525
+ extStatus,
526
+ appKey,
527
+ ancestorOrigin,
528
+ destinationOrigin,
529
+ callShow,
530
+ transferToLaborShow,
531
+ restShow,
532
+ networkDetectShow,
533
+ muteShow,
534
+ logShow,
535
+ timingShow,
536
+ signShow,
537
+ sign,
538
+ timestamp,
539
+ sgGateway,
540
+ sgBase,
541
+ sgOpen,
542
+ sgDomain,
543
+ } = this.option
544
+ let url = `${ envMap?.[env] ?? env }/#/?cross=1&open=1`
545
+ url += agentTag ? `&agentTag=${agentTag}` : ''
546
+ url += agentId ? `&agentId=${agentId}` : ''
547
+ url += appSecret ? `&appSecret=${appSecret}` : ''
548
+ url += sign ? `&sign=${sign}` : ''
549
+ url += timestamp ? `&timestamp=${timestamp}` : ''
550
+ url += sgGateway ? `&sgGateway=${sgGateway}` : ''
551
+ url += sgBase ? `&sgBase=${sgBase}` : ''
552
+ url += sgOpen ? `&sgOpen=${sgOpen}` : ''
553
+ url += sgDomain ? `&sgDomain=${sgDomain}` : ''
554
+ url += `&appKey=${appKey}&extStatus=${extStatus}&paddingTop=${top}px&paddingLeft=${left}px&ancestorOrigin=${ancestorOrigin}&destinationOrigin=${destinationOrigin}`
555
+ url += `&callShow=${callShow}`
556
+ url += `&transferToLaborShow=${transferToLaborShow}`
557
+ url += `&restShow=${restShow}`
558
+ url += `&networkDetectShow=${networkDetectShow}`
559
+ url += `&muteShow=${muteShow}`
560
+ url += `&logShow=${logShow}`
561
+ url += `&timingShow=${timingShow}`
562
+ url += `&signShow=${signShow}`
563
+ iframe.src = url
564
+ }
565
+
566
+ initSoftphone = (option) => {
567
+ this.option = option ? { ...this.option, ...option } :this.option
568
+ this.handlerInteractive()
569
+ this.handlerDomGenerater()
570
+ return this
571
+ }
572
+
573
+ postMessageCommunicate = (type, status) => {
574
+ const iframe = this.getSoftphoneDomElement('.nf-softphone-iframe')
575
+ if (iframe) {
576
+ iframe.contentWindow.postMessage(JSON.stringify({
577
+ origin: 'nf-softphone',
578
+ ancestorOrigin: this.option.ancestorOrigin,
579
+ destinationOrigin: this.option.destinationOrigin,
580
+ type,
581
+ data: {
582
+ status,
583
+ type: 'manual'
584
+ }
585
+ }), '*')
586
+ }
587
+ }
588
+ connect = () => {
589
+ this.postMessageCommunicate('softphone-connect', true)
590
+ }
591
+
592
+
593
+ disconnect = () => {
594
+ this.postMessageCommunicate('softphone-connect', false)
595
+ }
596
+
597
+ seatsStatusChange = (status) => {
598
+ this.postMessageCommunicate('softphone-seats-status-change', status)
599
+ }
600
+
601
+ accept = () => {
602
+ this.postMessageCommunicate('softphone-accept')
603
+ }
604
+ ignore = () => {
605
+ this.postMessageCommunicate('softphone-ignore')
606
+ }
607
+ hangup = () => {
608
+ this.postMessageCommunicate('softphone-hangup')
609
+ }
610
+ sendDtmf = () => {
611
+ this.postMessageCommunicate('softphone-send-dtmf')
612
+ }
613
+
614
+ removeTimer = () => {
615
+ clearTimeout(this.timer)
616
+ this.timer = undefined
617
+ }
618
+
619
+ destroy = () => {
620
+ window.removeEventListener('message', this.handlingCommunication, false);
621
+ if (this.option.scrollview) {
622
+ document.querySelector(this.option.scrollview)?.removeEventListener('scroll', this.handlingScroll)
623
+ document.querySelector(this.option.scrollview)?.removeEventListener('scroll', this.handlingScrollTransparent)
624
+ }
625
+ this.removeCssCode()
626
+ this.handlerDomRemove()
627
+ this.removeTimer()
628
+ }
629
+ }
630
+
631
+ const softphoneManager = new SoftphoneManager()
632
+
633
+ const agentTag1 = 'zoujh-test'
634
+ const appKey1 = '032d44009bff1752'
635
+ const appSecret1 = '4c7304c94c5e8725613516d4d6db679b'
636
+
637
+
638
+ /**
639
+ 👈 拿到实例后可以手动触发软电话实例的各种动作,如签入,签出,接听,忽略,挂断等等,注意这个实例不是软电话实例,是链接软电话通讯的实例
640
+ */
641
+ const nfSoftPhone1 = (new SoftphoneManager()).initSoftphone({ // 👈 当多实例时使用类创建新实例
642
+ el: '#softphone1', // 👈 软电话容器
643
+ selector: '#softpone-context1', // 👈 软电话容器查询上下文,用于多实例隔离软电话dom的查询环境
644
+ ancestorOrigin: 'nf-softphone1', // origin,ancestorOrigin,destinationOrigin具体含义参见下文,如果是单实例,可以写死origin=ai-softphone&destinationOrigin=nf-softphone&ancestorOrigin=nf-softphone
645
+ destinationOrigin: 'nf-softphone1',
646
+
647
+ agentTag: agentTag1, // 👈 坐席唯一标志
648
+ appKey: appKey1, // 👈 企业appKey
649
+ appSecret: appSecret1,
650
+ extStatus: '1', // 👈 初始化是否处于小休状态, 非1 小休 1 在线
651
+
652
+ softphoneConnectCallBack: (data) => {
653
+ console.log('softphone-connect')
654
+ }, // 👈 签入签出回调
655
+ softphoneCallRefreshCallBack: (data) => {console.log('softphone-call-refresh')}, // 👈 来电刷新来电记录列表回调
656
+ softphoneSeatStatusChangeCallBack: (data) => {console.log('softphone-seats-status-change')},// 👈 小休和在线切换回调
657
+ softphoneAcceptCallBack: (data) => {console.log('softphone-accept')},// 👈 接听回调
658
+ softphoneIgnoreCallBack: (data) => {console.log('softphone-ignore')},// 👈 忽略回调
659
+ softphoneHangupCallBack: (data) => {console.log('softphone-hangup')},// 👈 挂断回调
660
+ softphoneSessionStateChangeCallBack: (data) => {console.log('softphone-session-state-change')},// 👈 会话状态改变回调
661
+ softphoneIncomingCallBack: (data) => {console.log('softphone-incoming')},// 👈 来电回调
662
+ softphoneSendDtmfCallBack: (data) => {console.log('softphone-send-dtmf')},// 👈 转人工回调
663
+ softphoneConnectRegisteredCallBack: (data) => {console.log('softphone-connect-registered')},// 👈 动态绑定动作完成回调(在这之后才能执行动作类总线通讯)
664
+ });
665
+ </script>
666
+ </body>
667
+ </html>
@@ -823,7 +823,18 @@ document.addEventListener('DOMContentLoaded', async (event) => {
823
823
  *
824
824
  */
825
825
  const callPhone = async () => {
826
- const callInfo = await userAgentManager.callNumber(call.value) // 👈 一键
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
+ })
827
838
  if (callInfo.code === 200) { // 正确响应
828
839
  alert('呼叫成功')
829
840
  } else {