@94ai/softphone 4.0.1 → 5.0.2
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/index-colony.html +873 -0
- package/html-softphone-demo/index-test.html +23 -1
- package/html-softphone-demo/index.html +42 -27
- package/html-softphone-demo/softphone.umd.min.js +1 -2
- package/html-softphone-demo.zip +0 -0
- package/lib/index.d.ts +3 -0
- 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 +2 -2
- package/src/const/index.ts +0 -120
- package/src/index.ts +0 -123
- package/src/sdk/index.ts +0 -1986
- package/src/types/index.ts +0 -343
- package/src/utils/index.ts +0 -301
package/src/sdk/index.ts
DELETED
|
@@ -1,1986 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Inviter,
|
|
3
|
-
Registerer,
|
|
4
|
-
SessionState,
|
|
5
|
-
URI,
|
|
6
|
-
UserAgent
|
|
7
|
-
} from 'sip.js'
|
|
8
|
-
import { SessionDescriptionHandler } from 'sip.js/lib/api/session-description-handler'
|
|
9
|
-
import { UserAgentDelegate } from 'sip.js/lib/api/user-agent-delegate'
|
|
10
|
-
import { InvitationRejectOptions } from 'sip.js/lib/api/invitation-reject-options'
|
|
11
|
-
import { IncomingResponse } from 'sip.js/lib/core/messages/incoming-response'
|
|
12
|
-
import { RegistererUnregisterOptions } from 'sip.js/lib/api/registerer-unregister-options'
|
|
13
|
-
import { Invitation } from 'sip.js/lib/api/invitation'
|
|
14
|
-
import { OutgoingRequest } from 'sip.js/lib/core/messages/outgoing-request'
|
|
15
|
-
import {
|
|
16
|
-
ajaxOption,
|
|
17
|
-
HandUpInviteOption,
|
|
18
|
-
InvitationAcceptOptionsExtend as InvitationAcceptOptions,
|
|
19
|
-
InviterInviteOptionsExtend as InviterInviteOptions,
|
|
20
|
-
PrepareUserAgentOptions,
|
|
21
|
-
PushData,
|
|
22
|
-
RegisterOptions,
|
|
23
|
-
SessionInfoOptionsExtend as SessionInfoOptions,
|
|
24
|
-
UserAgentDelegateKey,
|
|
25
|
-
UserAgentOptions,
|
|
26
|
-
UserAgentStatusKey
|
|
27
|
-
} from '../types'
|
|
28
|
-
import {
|
|
29
|
-
AfterTransferLabour,
|
|
30
|
-
AiTaskVoiceType,
|
|
31
|
-
AutoAnswer,
|
|
32
|
-
userAgentDefault,
|
|
33
|
-
userAgentStatus
|
|
34
|
-
} from '../const'
|
|
35
|
-
import {
|
|
36
|
-
getAjax,
|
|
37
|
-
isDomainOrIP
|
|
38
|
-
} from '../utils'
|
|
39
|
-
import CryptoJS from 'crypto-js'
|
|
40
|
-
import onChange from 'on-change'
|
|
41
|
-
import FingerprintJS from '@fingerprintjs/fingerprintjs'
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 代理实例类
|
|
45
|
-
*/
|
|
46
|
-
export class UserAgentManager {
|
|
47
|
-
#extraHeaders: Array<string> | undefined
|
|
48
|
-
#userAgent: InstanceType<typeof UserAgent> | undefined = undefined
|
|
49
|
-
#registerer: Registerer | undefined = undefined
|
|
50
|
-
#sessionDescriptionHandler: SessionDescriptionHandler | undefined = undefined
|
|
51
|
-
#peerConnection: RTCPeerConnection | undefined = undefined
|
|
52
|
-
#receivers: RTCRtpReceiver[] | [] = []
|
|
53
|
-
#senders: RTCRtpSender[] | [] = []
|
|
54
|
-
#userAgentOption: Partial<UserAgentOptions> = {}
|
|
55
|
-
#currentInvitation: Invitation | undefined = undefined
|
|
56
|
-
#currentInviter: InstanceType<typeof Inviter> | undefined = undefined
|
|
57
|
-
#stream: MediaStream | undefined = new MediaStream()
|
|
58
|
-
#userAgentStatus: typeof userAgentStatus = JSON.parse(JSON.stringify(userAgentStatus))
|
|
59
|
-
#volumeValue = 0.00
|
|
60
|
-
#sign: string
|
|
61
|
-
#timestamp: string | number
|
|
62
|
-
#token: string
|
|
63
|
-
token: string
|
|
64
|
-
#tokenTimestamp: string | number
|
|
65
|
-
#signExpirationTime = 8 * 60 * 1000
|
|
66
|
-
#tokenExpirationTime = 200 * 60 * 1000
|
|
67
|
-
#pushData: Record<string, PushData> = {}
|
|
68
|
-
/**
|
|
69
|
-
* 坐席讲话音量
|
|
70
|
-
*/
|
|
71
|
-
public get volumeValue () {
|
|
72
|
-
return this.#volumeValue
|
|
73
|
-
}
|
|
74
|
-
#mediaStreamAudioSourceNode: MediaStreamAudioSourceNode
|
|
75
|
-
#scriptProcessorNode :ScriptProcessorNode
|
|
76
|
-
#mediaStream: MediaStream
|
|
77
|
-
#refreshSpeekVolumn: Function = (param: number) => {}
|
|
78
|
-
#refreshChat: Function = (pushData: PushData) => {}
|
|
79
|
-
#openXhrIntercept: Function = (xhr: any) => {}
|
|
80
|
-
#gatewayXhrIntercept: Function = (xhr: any) => {}
|
|
81
|
-
#signCheck: Function = async (expirationTime: number) => {
|
|
82
|
-
if (this.#sign && this.#timestamp) {
|
|
83
|
-
if (Date.now() - (this.#timestamp as number) > expirationTime) {
|
|
84
|
-
const { sign, timestamp } = await this.#signOverdued()
|
|
85
|
-
this.#sign = sign ?? this.#sign
|
|
86
|
-
this.#timestamp = timestamp ?? this.#timestamp
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
#tokenCheck: Function = async (expirationTime: number) => {
|
|
91
|
-
if (this.#tokenTimestamp) {
|
|
92
|
-
if (Date.now() - (this.#tokenTimestamp as number) > expirationTime) {
|
|
93
|
-
if (this.#token) {
|
|
94
|
-
const { token, tokenTimestamp } = await this.#tokenOverdued()
|
|
95
|
-
this.#token = token ?? this.#token
|
|
96
|
-
this.#tokenTimestamp = tokenTimestamp ?? Date.now()
|
|
97
|
-
} else if (this.token) {
|
|
98
|
-
const {
|
|
99
|
-
token
|
|
100
|
-
} = await this.getGatewayAccessToken({
|
|
101
|
-
corpId: this.#appKey,
|
|
102
|
-
sid: this.#agentId,
|
|
103
|
-
secret: this.#appSecret,
|
|
104
|
-
seatOnline: false
|
|
105
|
-
})
|
|
106
|
-
this.token = token
|
|
107
|
-
this.#tokenTimestamp = Date.now()
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
#signOverdued: Function = () => {}
|
|
113
|
-
#tokenOverdued: Function = () => {}
|
|
114
|
-
#refreshChatErrorCallback: Function = (pushData: PushData) => {}
|
|
115
|
-
#refreshRequirementCheck: Function = (param: {
|
|
116
|
-
networkSpeed: string
|
|
117
|
-
networkDelay: string
|
|
118
|
-
authorizedMicrophonePermissions: boolean
|
|
119
|
-
authorizedNotificationPermissions: boolean
|
|
120
|
-
}) => {}
|
|
121
|
-
|
|
122
|
-
#testingTimer: ReturnType<typeof setTimeout> | undefined // 测速定时器
|
|
123
|
-
#oneRoundOfTestingTimer: ReturnType<typeof setTimeout> | undefined // 单轮检测定时器
|
|
124
|
-
public networkSpeed = '0.00Mb/s'// 带宽速度
|
|
125
|
-
public networkDelay = '0ms' // 网络延迟
|
|
126
|
-
public onLine = navigator.onLine // 是否在线
|
|
127
|
-
public testspeeding = false // 测速中
|
|
128
|
-
public goodNetworkSpeed = (): boolean => { // 网络状态是否良好
|
|
129
|
-
return Number(this.networkSpeed.replace('Mb/s', '')) * 1000 >= 30
|
|
130
|
-
}
|
|
131
|
-
public goodNetworkDelay = (): boolean => { // 网络状态是否良好
|
|
132
|
-
return Number(this.networkDelay.replace('ms', '')) <= 500 && this.networkDelay !== '0ms'
|
|
133
|
-
}
|
|
134
|
-
public authorizedMicrophonePermissions = false
|
|
135
|
-
public authorizedNotificationPermissions = false
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* 检测端服务端宕机,重启,网络抖动出现服务不可达时配置重连
|
|
139
|
-
* */
|
|
140
|
-
/** 重连最大次数 */
|
|
141
|
-
#reconnectionAttempts = 100
|
|
142
|
-
/** 重连间隔 */
|
|
143
|
-
#reconnectionInterval = 3
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* 检测服务端保活,默认多久发一次sip options信号,检查服务端状态是否可达
|
|
147
|
-
* */
|
|
148
|
-
#optionsPingInterval = 8
|
|
149
|
-
/** 可能由于网络抖动导致ping失败,(这个时候用户可能在接听电话,不要重连影响通话)这个时候服务器是正常,增加尝试次数确保服务器是真的在重启 */
|
|
150
|
-
#optionsPingAttempts = 3
|
|
151
|
-
#optionsPingAttempt = 0
|
|
152
|
-
#optionsPingFailure = false
|
|
153
|
-
#optionsPingRequest: OutgoingRequest
|
|
154
|
-
/** 重新注册间隔 */
|
|
155
|
-
#registerInterval = 3
|
|
156
|
-
#registerAttempts = 3
|
|
157
|
-
#registerAttempt = 0
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* 客户端保活,默认多久发一次"\r\n\r\n"socket信号,告诉服务端客户端还在,不要发送close信号
|
|
161
|
-
* */
|
|
162
|
-
#keepAliveInterval = 0
|
|
163
|
-
#keepAliveDebounce = 0
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* 节流用户可能手动触发
|
|
167
|
-
* */
|
|
168
|
-
#reconnectionAttempting = false
|
|
169
|
-
#optionsPingRunning = false
|
|
170
|
-
#registerAttempting = false
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* 终止用户可能手动结束
|
|
174
|
-
* */
|
|
175
|
-
#reconnectionTimer: ReturnType<typeof setTimeout> | undefined
|
|
176
|
-
#optionsPingTimer: ReturnType<typeof setTimeout> | undefined
|
|
177
|
-
#registerTimer: ReturnType<typeof setTimeout> | undefined
|
|
178
|
-
|
|
179
|
-
#appKey: string
|
|
180
|
-
#appSecret: string
|
|
181
|
-
#agentTag: string
|
|
182
|
-
#agentId: number
|
|
183
|
-
#openBaseUrl?: string
|
|
184
|
-
#sgOpen?: '0' | '1'
|
|
185
|
-
#sg?: '0' | '1'
|
|
186
|
-
#visitorId?: string
|
|
187
|
-
|
|
188
|
-
/** 断网重连 */
|
|
189
|
-
#attemptReconnectionHandler = (e: Event) => {
|
|
190
|
-
this.#attemptReconnection()
|
|
191
|
-
}
|
|
192
|
-
#empty = () => {
|
|
193
|
-
}
|
|
194
|
-
#businessAttribute: Record<string, any> = {}
|
|
195
|
-
|
|
196
|
-
public get businessAttribute () {
|
|
197
|
-
return this.#businessAttribute
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
#enableChatInfoPush = false
|
|
201
|
-
#enableVolumnTrack = true
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* 每次来电是否自动接听
|
|
205
|
-
*/
|
|
206
|
-
public ifAutoAnswer = () => {
|
|
207
|
-
return String(this.#businessAttribute.autoAnswer) === AutoAnswer.YES
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* 每次来电是否需要转人工
|
|
212
|
-
*/
|
|
213
|
-
public notNeedSendStarDtmf = () => {
|
|
214
|
-
return String(this.#businessAttribute.afterTransferLabour) === AfterTransferLabour.DIRECT_ANSWER
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* 是否快速外呼
|
|
219
|
-
*/
|
|
220
|
-
getFastOutboundCall() {
|
|
221
|
-
return this.#businessAttribute.voiceType === AiTaskVoiceType.LABOUR
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
getSitOutboundCall() {
|
|
226
|
-
return this.#businessAttribute.voiceType === AiTaskVoiceType.AI
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
constructor (userAgentOption: Partial<UserAgentOptions> = {}) {
|
|
230
|
-
if (typeof userAgentOption !== 'object') {
|
|
231
|
-
throw new Error('userAgentOption must be plain object')
|
|
232
|
-
}
|
|
233
|
-
this.#initOpenApiOption(userAgentOption)
|
|
234
|
-
this.#initGatewayApiOption(userAgentOption)
|
|
235
|
-
this.#initExtraHeaders(userAgentOption)
|
|
236
|
-
this.#initKeepAliveOptions(userAgentOption)
|
|
237
|
-
this.#initRefreshCallback(userAgentOption)
|
|
238
|
-
this.#initIntercept(userAgentOption)
|
|
239
|
-
this.#userAgentOption = this.#getUserAgentOption(userAgentOption)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
#trackVolume = () => {
|
|
244
|
-
if (this.#enableVolumnTrack) {
|
|
245
|
-
this.#stopTrackVolume()
|
|
246
|
-
// @ts-ignore
|
|
247
|
-
const AudioContext = window.AudioContext || window.webkitAudioContext
|
|
248
|
-
const audioContext = new AudioContext()
|
|
249
|
-
this.#scriptProcessorNode = audioContext.createScriptProcessor(2048, 1, 1)
|
|
250
|
-
this.#scriptProcessorNode.onaudioprocess = (event: AudioProcessingEvent) => {
|
|
251
|
-
const input = event.inputBuffer.getChannelData(0) // 得到一个长度为2048的数组
|
|
252
|
-
let sum = 0.00
|
|
253
|
-
for (let i = 0; i < input.length; ++i) {
|
|
254
|
-
sum += input[i] * input[i]
|
|
255
|
-
}
|
|
256
|
-
this.#volumeValue = Number(Math.sqrt(sum / input.length).toFixed(2))
|
|
257
|
-
this.#refreshSpeekVolumn(this.#volumeValue)
|
|
258
|
-
}
|
|
259
|
-
this.#mediaStream = new MediaStream()
|
|
260
|
-
this.getSenders().forEach((sender) => {
|
|
261
|
-
if (sender.track) {
|
|
262
|
-
this.#mediaStream.addTrack(sender.track)
|
|
263
|
-
}
|
|
264
|
-
})
|
|
265
|
-
this.#mediaStreamAudioSourceNode = audioContext.createMediaStreamSource(this.#mediaStream)
|
|
266
|
-
this.#mediaStreamAudioSourceNode.connect(this.#scriptProcessorNode)
|
|
267
|
-
this.#scriptProcessorNode.connect(audioContext.destination)
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
#stopTrackVolume = () => {
|
|
272
|
-
if (this.#enableVolumnTrack) {
|
|
273
|
-
if (this.#mediaStreamAudioSourceNode) {
|
|
274
|
-
try {
|
|
275
|
-
this.#mediaStreamAudioSourceNode.disconnect()
|
|
276
|
-
} catch (e) {
|
|
277
|
-
console.log(e)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (this.#scriptProcessorNode) {
|
|
281
|
-
try {
|
|
282
|
-
this.#scriptProcessorNode.disconnect()
|
|
283
|
-
} catch (e) {
|
|
284
|
-
console.log(e)
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (this.#mediaStream) {
|
|
288
|
-
try {
|
|
289
|
-
this.#mediaStream.getTracks().forEach(track => {
|
|
290
|
-
track.stop()
|
|
291
|
-
})
|
|
292
|
-
} catch (e) {
|
|
293
|
-
console.log(e)
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
this.#mediaStreamAudioSourceNode = undefined
|
|
298
|
-
this.#scriptProcessorNode = undefined
|
|
299
|
-
this.#mediaStream = undefined
|
|
300
|
-
this.#volumeValue = 0.00
|
|
301
|
-
this.#refreshSpeekVolumn(this.#volumeValue)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
public disposeSoftphoneEnvRequirementCheck = () => {
|
|
305
|
-
this.#clearSoftphoneEnvRequirementTimer()
|
|
306
|
-
// this.networkSpeed = '0.00Mb/s'
|
|
307
|
-
// this.networkDelay = '0ms'
|
|
308
|
-
window.removeEventListener('offline', this.#offLineAction)
|
|
309
|
-
window.removeEventListener('online', this.#onlineAction)
|
|
310
|
-
window.removeEventListener('unload', this.disposeSoftphoneEnvRequirementCheck)
|
|
311
|
-
}
|
|
312
|
-
#clearSoftphoneEnvRequirementTimer = () => {
|
|
313
|
-
clearInterval(this.#testingTimer)
|
|
314
|
-
this.#testingTimer = undefined
|
|
315
|
-
clearTimeout(this.#oneRoundOfTestingTimer)
|
|
316
|
-
this.#oneRoundOfTestingTimer = undefined
|
|
317
|
-
this.testspeeding = false
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* 软电话环境检测
|
|
322
|
-
*/
|
|
323
|
-
public softphoneEnvCheck = async () => {
|
|
324
|
-
if (!this.testspeeding) {
|
|
325
|
-
this.#realTimeSyncStatus()
|
|
326
|
-
this.oneRoundOfTesting()
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
#realTimeSyncStatus = () => {
|
|
330
|
-
window.addEventListener('offline', this.#offLineAction)
|
|
331
|
-
window.addEventListener('online', this.#onlineAction)
|
|
332
|
-
window.addEventListener('unload', this.disposeSoftphoneEnvRequirementCheck)
|
|
333
|
-
}
|
|
334
|
-
public oneRoundOfTesting = async () => {
|
|
335
|
-
this.testspeeding = true
|
|
336
|
-
this.detectNavigatorPermissions()
|
|
337
|
-
if (this.onLine) {
|
|
338
|
-
this.#detectNetwork(this)
|
|
339
|
-
this.#oneRoundOfTestingTimer = setTimeout(() => {
|
|
340
|
-
this.testspeeding = false
|
|
341
|
-
this.disposeSoftphoneEnvRequirementCheck()
|
|
342
|
-
}, 4000)
|
|
343
|
-
} else {
|
|
344
|
-
this.testspeeding = false
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
#onlineAction = () => {
|
|
349
|
-
this.onLine = true
|
|
350
|
-
this.oneRoundOfTesting()
|
|
351
|
-
}
|
|
352
|
-
#offLineAction = () => {
|
|
353
|
-
this.onLine = false
|
|
354
|
-
this.networkSpeed = '0.00Mb/s'
|
|
355
|
-
this.networkDelay = '0ms'
|
|
356
|
-
this.#refreshRequirementCheck({
|
|
357
|
-
networkSpeed: this.networkSpeed,
|
|
358
|
-
networkDelay: this.networkDelay,
|
|
359
|
-
authorizedMicrophonePermissions: this.authorizedMicrophonePermissions,
|
|
360
|
-
authorizedNotificationPermissions: this.authorizedNotificationPermissions
|
|
361
|
-
})
|
|
362
|
-
this.#clearSoftphoneEnvRequirementTimer()
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* 检测麦克风权限
|
|
367
|
-
*/
|
|
368
|
-
public detectNavigatorPermissions () {
|
|
369
|
-
if (navigator.userAgent.indexOf('Firefox') === -1) {
|
|
370
|
-
// @ts-ignore
|
|
371
|
-
navigator.permissions.query({ name: 'microphone' })
|
|
372
|
-
.then((permissionObj) => {
|
|
373
|
-
if (permissionObj.state === 'granted') {
|
|
374
|
-
this.authorizedMicrophonePermissions = true
|
|
375
|
-
} else {
|
|
376
|
-
this.authorizedMicrophonePermissions = false
|
|
377
|
-
}
|
|
378
|
-
})
|
|
379
|
-
.catch((error) => {
|
|
380
|
-
console.log('Got error :', error)
|
|
381
|
-
this.authorizedMicrophonePermissions = false
|
|
382
|
-
})
|
|
383
|
-
} else {
|
|
384
|
-
navigator.mediaDevices.enumerateDevices().then(info => {
|
|
385
|
-
if (info.filter(({ kind }) => kind === 'audioinput')[0].label !== '') {
|
|
386
|
-
this.authorizedMicrophonePermissions = true
|
|
387
|
-
} else {
|
|
388
|
-
this.authorizedMicrophonePermissions = false
|
|
389
|
-
}
|
|
390
|
-
})
|
|
391
|
-
}
|
|
392
|
-
navigator.permissions.query({ name: 'notifications' })
|
|
393
|
-
.then((permissionObj) => {
|
|
394
|
-
if (permissionObj.state === 'granted') {
|
|
395
|
-
this.authorizedNotificationPermissions = true
|
|
396
|
-
} else {
|
|
397
|
-
this.authorizedNotificationPermissions = false
|
|
398
|
-
}
|
|
399
|
-
})
|
|
400
|
-
.catch((error) => {
|
|
401
|
-
console.log('Got error :', error)
|
|
402
|
-
this.authorizedNotificationPermissions = false
|
|
403
|
-
})
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
#detectNetwork = async (instance?: InstanceType<typeof UserAgentManager>) => {
|
|
407
|
-
const that = instance || this
|
|
408
|
-
clearInterval(that.#testingTimer)
|
|
409
|
-
that.#testingTimer = undefined
|
|
410
|
-
const testNetWork = async () => {
|
|
411
|
-
if (that.onLine) {
|
|
412
|
-
try {
|
|
413
|
-
const result = await that.#getNetSpeed()
|
|
414
|
-
that.networkSpeed = result.networkSpeed
|
|
415
|
-
that.networkDelay = result.networkDelay
|
|
416
|
-
} catch (err) {
|
|
417
|
-
that.networkSpeed = '0.00Mb/s'
|
|
418
|
-
that.networkDelay = '0ms'
|
|
419
|
-
}
|
|
420
|
-
} else {
|
|
421
|
-
that.networkSpeed = '0.00Mb/s'
|
|
422
|
-
that.networkDelay = '0ms'
|
|
423
|
-
}
|
|
424
|
-
this.#refreshRequirementCheck({
|
|
425
|
-
networkSpeed: this.networkSpeed,
|
|
426
|
-
networkDelay: this.networkDelay,
|
|
427
|
-
authorizedMicrophonePermissions: this.authorizedMicrophonePermissions,
|
|
428
|
-
authorizedNotificationPermissions: this.authorizedNotificationPermissions
|
|
429
|
-
})
|
|
430
|
-
if (!this.#oneRoundOfTestingTimer) {
|
|
431
|
-
this.testspeeding = false
|
|
432
|
-
this.disposeSoftphoneEnvRequirementCheck()
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
await testNetWork()
|
|
436
|
-
if (this.#oneRoundOfTestingTimer) {
|
|
437
|
-
that.#testingTimer = setInterval(async function () {
|
|
438
|
-
await testNetWork()
|
|
439
|
-
}, 1000)
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
#getNetSpeed = async (imgUrl: string = ('https://seat.94ai.com/favicon.png'), fileSize: number = 63491, times = 1) => {
|
|
444
|
-
// @ts-ignore
|
|
445
|
-
// const connection = window.navigator.connection // 该api火狐和苹果浏览器不支持,而且不准
|
|
446
|
-
// if (connection && connection.networkSpeed) {
|
|
447
|
-
// return connection.networkSpeed + 'Mb/s'
|
|
448
|
-
// }
|
|
449
|
-
// 多次测速求平均值
|
|
450
|
-
const arr = []
|
|
451
|
-
for (let i = 0; i < times; i++) {
|
|
452
|
-
arr.push(this.#getSpeedWithImg(imgUrl + '?d=' + Date.now() + i, fileSize))
|
|
453
|
-
}
|
|
454
|
-
return Promise.all(arr).then(speeds => {
|
|
455
|
-
let sum = 0
|
|
456
|
-
let sumSpeed = 0
|
|
457
|
-
speeds.forEach((speed:any) => {
|
|
458
|
-
sum += Number(speed[0])
|
|
459
|
-
sumSpeed += Number(speed[1])
|
|
460
|
-
})
|
|
461
|
-
return {
|
|
462
|
-
networkDelay: (sumSpeed / times).toFixed(2) + 'ms',
|
|
463
|
-
networkSpeed: (sum / times).toFixed(2) + 'Mb/s'
|
|
464
|
-
}
|
|
465
|
-
})
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
#getSpeedWithImg = (imgUrl: string = ('https://seat.94ai.com/favicon.png?d=' + Date.now()), fileSize: number = 63491) => {
|
|
469
|
-
return new Promise((resolve, reject) => {
|
|
470
|
-
const img: any = document.createElement('img')
|
|
471
|
-
img.start = window.performance.now()
|
|
472
|
-
img.onload = function () {
|
|
473
|
-
const end = window.performance.now()
|
|
474
|
-
const delay = end - img.start
|
|
475
|
-
const speed = (fileSize * 1000) / (1024 * 1024 * delay)
|
|
476
|
-
resolve([speed.toFixed(2), delay])
|
|
477
|
-
}
|
|
478
|
-
img.onerror = function (e: Error) {
|
|
479
|
-
reject(e)
|
|
480
|
-
}
|
|
481
|
-
img.src = imgUrl
|
|
482
|
-
}).catch(err => {
|
|
483
|
-
throw err
|
|
484
|
-
})
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
public openApiAjax = (param: Partial<ajaxOption>): Promise<any>=>{
|
|
488
|
-
return new Promise(async (resolve, reject) => {
|
|
489
|
-
const { xhr, ajaxData, contentType } = getAjax(resolve, reject, param)
|
|
490
|
-
const { timestamp, sign } = await this.getOpenApiSign()
|
|
491
|
-
xhr.setRequestHeader('appKey', this.#appKey)
|
|
492
|
-
xhr.setRequestHeader('timestamp', String(timestamp))
|
|
493
|
-
xhr.setRequestHeader('sign', sign)
|
|
494
|
-
xhr.setRequestHeader("Content-type",contentType || "application/json")
|
|
495
|
-
await this.#openXhrIntercept(xhr)
|
|
496
|
-
xhr.send(ajaxData)
|
|
497
|
-
})
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
public getGateway() {
|
|
501
|
-
if (this.#sg === '1') {
|
|
502
|
-
return 'https://gateway.sg.94ai.com'
|
|
503
|
-
}
|
|
504
|
-
// return 'http://gateway.test.k8s.com:31962'
|
|
505
|
-
return 'https://gateway.94ai.com'
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
public requestGateway = (param: Partial<ajaxOption>, needToken = true) => {
|
|
509
|
-
return this.gatewayApiAjax({
|
|
510
|
-
...param,
|
|
511
|
-
url: this.getGateway() + param.url,
|
|
512
|
-
}, needToken)
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
public gatewayApiAjax = (param: Partial<ajaxOption>, needToken = true): Promise<any>=>{
|
|
516
|
-
return new Promise(async (resolve, reject) => {
|
|
517
|
-
const { xhr, ajaxData, contentType } = getAjax(resolve, reject, param)
|
|
518
|
-
if (needToken) {
|
|
519
|
-
const token = await this.getGatewayToken()
|
|
520
|
-
xhr.setRequestHeader('x-token', token)
|
|
521
|
-
xhr.setRequestHeader('seatsToken', token)
|
|
522
|
-
xhr.setRequestHeader('x-authority-token', token)
|
|
523
|
-
}
|
|
524
|
-
xhr.setRequestHeader('x-app-code', 3)
|
|
525
|
-
xhr.setRequestHeader("content-type",contentType || "application/json")
|
|
526
|
-
await this.#gatewayXhrIntercept(xhr)
|
|
527
|
-
xhr.send(ajaxData)
|
|
528
|
-
})
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
public async getGatewayToken() {
|
|
532
|
-
await this.#tokenCheck(this.#tokenExpirationTime)
|
|
533
|
-
if (this.#token) {
|
|
534
|
-
return this.#token
|
|
535
|
-
}
|
|
536
|
-
if (!this.token) {
|
|
537
|
-
await this.#openApiRequirementAssign()
|
|
538
|
-
const {
|
|
539
|
-
token
|
|
540
|
-
} = await this.getGatewayAccessToken({
|
|
541
|
-
corpId: this.#appKey,
|
|
542
|
-
sid: this.#agentId,
|
|
543
|
-
secret: this.#appSecret,
|
|
544
|
-
seatOnline: false
|
|
545
|
-
})
|
|
546
|
-
this.token = token
|
|
547
|
-
this.#tokenTimestamp = Date.now()
|
|
548
|
-
}
|
|
549
|
-
return this.token
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
public getGatewayAccessToken(params: {
|
|
553
|
-
corpId: string,
|
|
554
|
-
secret: string,
|
|
555
|
-
sid: number,
|
|
556
|
-
seatOnline: boolean
|
|
557
|
-
}){
|
|
558
|
-
return this.requestGateway({
|
|
559
|
-
url: '/authority/accessToken/openApi',
|
|
560
|
-
data: params,
|
|
561
|
-
}, false)
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
public getCallNumberDetail(params: {
|
|
565
|
-
id: string,
|
|
566
|
-
taskId: string
|
|
567
|
-
}){
|
|
568
|
-
return this.requestGateway({
|
|
569
|
-
url: '/task-aggre/callCenterNumber/get',
|
|
570
|
-
data: params,
|
|
571
|
-
})
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
public getCallNumberChats(params: {
|
|
575
|
-
callId: string,
|
|
576
|
-
taskId: string
|
|
577
|
-
}){
|
|
578
|
-
return this.requestGateway({
|
|
579
|
-
url: '/dialogue-aggre/call-center-number/chat/list',
|
|
580
|
-
data: params,
|
|
581
|
-
})
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
async getCallInfo() {
|
|
585
|
-
const callIdPushData: any = {
|
|
586
|
-
...this.#businessAttribute
|
|
587
|
-
}
|
|
588
|
-
const polling = async () => {
|
|
589
|
-
if (callIdPushData.numberId && callIdPushData.taskId) {
|
|
590
|
-
let result: Record<string, any> = {}
|
|
591
|
-
try {
|
|
592
|
-
result = (await this.getCallNumberDetail({ id: callIdPushData.numberId, taskId: callIdPushData.taskId }))
|
|
593
|
-
if (result.code !== 200) {
|
|
594
|
-
console.error(result.message)
|
|
595
|
-
callIdPushData.stop = true
|
|
596
|
-
this.#refreshChatErrorCallback(new Error(result.message))
|
|
597
|
-
return
|
|
598
|
-
}
|
|
599
|
-
} catch (e) {
|
|
600
|
-
const message = `当前会话callid:${callIdPushData.callId},根据numberId:${callIdPushData.numberId},taskId:${callIdPushData.taskId}:查询坐席接听状态异常`
|
|
601
|
-
console.error(message)
|
|
602
|
-
callIdPushData.stop = true
|
|
603
|
-
this.#refreshChatErrorCallback(new Error(result.message))
|
|
604
|
-
return
|
|
605
|
-
}
|
|
606
|
-
const {
|
|
607
|
-
callType,
|
|
608
|
-
newIntentTag,
|
|
609
|
-
intentTag,
|
|
610
|
-
number,
|
|
611
|
-
numberMd5,
|
|
612
|
-
sid,
|
|
613
|
-
config,
|
|
614
|
-
tag,
|
|
615
|
-
state
|
|
616
|
-
} = result.data;
|
|
617
|
-
if (state === 10) { // 用户已挂机
|
|
618
|
-
console.log(`当前会话callid:${callIdPushData.callId}已结束`)
|
|
619
|
-
callIdPushData.stop = true
|
|
620
|
-
// this.#sessionEndCallback(pushData.callid!)
|
|
621
|
-
return
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
callIdPushData.callType ||= callType
|
|
625
|
-
callIdPushData.intentTag = newIntentTag || intentTag
|
|
626
|
-
callIdPushData.number ||= number
|
|
627
|
-
callIdPushData.numberMD5 ||= numberMd5
|
|
628
|
-
callIdPushData.agentId ||= sid
|
|
629
|
-
callIdPushData.tag ||= tag
|
|
630
|
-
|
|
631
|
-
if (!callIdPushData.templateId) {
|
|
632
|
-
if (config && typeof config === 'string') {
|
|
633
|
-
const configP = JSON.parse(config)
|
|
634
|
-
if (configP && configP.templateId) {
|
|
635
|
-
callIdPushData.templateId = configP.templateId
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
if (callIdPushData.callId) {
|
|
640
|
-
let res: Record<string, any> = {}
|
|
641
|
-
try {
|
|
642
|
-
res = await this.getCallNumberChats({
|
|
643
|
-
callId: callIdPushData.callId,
|
|
644
|
-
taskId: callIdPushData.taskId
|
|
645
|
-
})
|
|
646
|
-
if (res.code !== 200) {
|
|
647
|
-
console.error(res.message)
|
|
648
|
-
callIdPushData.stop = true
|
|
649
|
-
this.#refreshChatErrorCallback(new Error(res.message))
|
|
650
|
-
return
|
|
651
|
-
}
|
|
652
|
-
} catch (e) {
|
|
653
|
-
const message = `当前会话根据callId:${callIdPushData.callId},taskId:${callIdPushData.taskId}:查询会话对话记录异常`
|
|
654
|
-
console.error(message)
|
|
655
|
-
callIdPushData.stop = true
|
|
656
|
-
this.#refreshChatErrorCallback(new Error(message))
|
|
657
|
-
return
|
|
658
|
-
}
|
|
659
|
-
callIdPushData.chats = res.data.map((v: Record<string, any>) => {
|
|
660
|
-
if (v.matchinfo) {
|
|
661
|
-
v.matchinfo = JSON.parse(v.matchinfo)
|
|
662
|
-
}
|
|
663
|
-
return v
|
|
664
|
-
})
|
|
665
|
-
this.#pushData[callIdPushData.callId] = callIdPushData
|
|
666
|
-
}
|
|
667
|
-
try {
|
|
668
|
-
this.#refreshChat(this.#pushData[callIdPushData.callId])
|
|
669
|
-
} catch (e) {
|
|
670
|
-
console.log(e)
|
|
671
|
-
}
|
|
672
|
-
} else {
|
|
673
|
-
callIdPushData.stop = true
|
|
674
|
-
if (!callIdPushData.taskId) {
|
|
675
|
-
const message = `当前会话callid:${callIdPushData.callId}的taskId不存在,无法查询通话记录`
|
|
676
|
-
console.error(message)
|
|
677
|
-
this.#refreshChatErrorCallback(new Error(message))
|
|
678
|
-
}
|
|
679
|
-
if (!callIdPushData.numberId) {
|
|
680
|
-
const message = `当前会话callid:${callIdPushData.callId}的numberId不存在,无法查询通话记录`
|
|
681
|
-
console.error(message)
|
|
682
|
-
this.#refreshChatErrorCallback(new Error(message))
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
try {
|
|
687
|
-
while (true) {
|
|
688
|
-
if (callIdPushData.stop) break
|
|
689
|
-
await polling()
|
|
690
|
-
if (callIdPushData.stop) break
|
|
691
|
-
await new Promise((resolve) => {
|
|
692
|
-
let refreshMessageTimer: ReturnType<typeof setTimeout> | undefined = setTimeout(async () => {
|
|
693
|
-
clearTimeout(refreshMessageTimer)
|
|
694
|
-
refreshMessageTimer = undefined
|
|
695
|
-
resolve(true)
|
|
696
|
-
}, 3000)
|
|
697
|
-
})
|
|
698
|
-
}
|
|
699
|
-
} catch (e) {
|
|
700
|
-
this.#refreshChatErrorCallback(e as Error)
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
async refreshCallChatInfo() {
|
|
705
|
-
if (this.#businessAttribute.numberId && this.#businessAttribute.callId && this.#businessAttribute.taskId && this.#enableChatInfoPush) {
|
|
706
|
-
await this.getCallInfo()
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
public requestOpenApi = (param: Partial<ajaxOption>) => {
|
|
711
|
-
return this.openApiAjax({
|
|
712
|
-
...param,
|
|
713
|
-
url: this.getOpenApi() + param.url,
|
|
714
|
-
})
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
public getOpenApi () {
|
|
718
|
-
if (this.#openBaseUrl) {
|
|
719
|
-
if (isDomainOrIP(this.#openBaseUrl)) {
|
|
720
|
-
return this.#openBaseUrl
|
|
721
|
-
}
|
|
722
|
-
if (this.#sg === '1') {
|
|
723
|
-
if (this.#sgOpen === '1') {
|
|
724
|
-
return 'https://seatsg.94ai.com/sgopenapi'
|
|
725
|
-
} else {
|
|
726
|
-
return 'https://seatsg.94ai.com/openapi'
|
|
727
|
-
}
|
|
728
|
-
} else {
|
|
729
|
-
if (this.#sgOpen === '1') {
|
|
730
|
-
return 'https://seat.94ai.com/sgopenapi'
|
|
731
|
-
} else {
|
|
732
|
-
return 'https://seat.94ai.com/openapi'
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return 'https://seat.94ai.com/openapi'
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
public getAgentInfo () {
|
|
740
|
-
return this.requestOpenApi({
|
|
741
|
-
url: '/v1/agent/getAgent',
|
|
742
|
-
data: {
|
|
743
|
-
agentTag: this.#agentTag,
|
|
744
|
-
agentId: this.#agentId
|
|
745
|
-
}
|
|
746
|
-
})
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
public async getOpenApiSign(timestamp = Date.now(), appKey?: string, appSecret?: string) {
|
|
750
|
-
await this.#signCheck(this.#signExpirationTime)
|
|
751
|
-
return {
|
|
752
|
-
sign: this.#sign ?? CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA1(appKey || this.#appKey + timestamp, appSecret || this.#appSecret)),
|
|
753
|
-
timestamp: this.#timestamp ?? timestamp
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
public updateOpenApiAgentStatus (agentStatus: 1 | 2 | 3 | 4) {
|
|
758
|
-
return this.requestOpenApi({
|
|
759
|
-
url: '/v1/agent/updateAgentStatus',
|
|
760
|
-
data: {
|
|
761
|
-
agentTag: this.#agentTag,
|
|
762
|
-
agentId: this.#agentId,
|
|
763
|
-
agentStatus
|
|
764
|
-
}
|
|
765
|
-
})
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
public importOpenApiAgentCustomer (number: string) {
|
|
769
|
-
return this.requestOpenApi({
|
|
770
|
-
url: '/v1/task/importAgentCustomer',
|
|
771
|
-
data: {
|
|
772
|
-
agentTag: this.#agentTag,
|
|
773
|
-
agentId: this.#agentId,
|
|
774
|
-
callType: 1001,
|
|
775
|
-
customers: [{ number }]
|
|
776
|
-
}
|
|
777
|
-
})
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
/**
|
|
781
|
-
* 呼叫
|
|
782
|
-
*/
|
|
783
|
-
public callNumber(number: string) {
|
|
784
|
-
return this.importOpenApiAgentCustomer(number)
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
/**
|
|
788
|
-
* 小休
|
|
789
|
-
*/
|
|
790
|
-
public toggleNap(nap: boolean) {
|
|
791
|
-
if (nap) {
|
|
792
|
-
return this.updateOpenApiAgentStatus(3)
|
|
793
|
-
}
|
|
794
|
-
return this.updateOpenApiAgentStatus(1)
|
|
795
|
-
}
|
|
796
|
-
#initIntercept(userAgentOption: Partial<UserAgentOptions> = {}) {
|
|
797
|
-
this.#openXhrIntercept = userAgentOption.openXhrIntercept ?? this.#openXhrIntercept
|
|
798
|
-
this.#gatewayXhrIntercept = userAgentOption.gatewayXhrIntercept ?? this.#gatewayXhrIntercept
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
#initRefreshCallback(userAgentOption: Partial<UserAgentOptions> = {}) {
|
|
802
|
-
this.#enableChatInfoPush = userAgentOption.enableChatInfoPush ?? this.#enableChatInfoPush
|
|
803
|
-
this.#enableVolumnTrack = userAgentOption.enableVolumnTrack ?? this.#enableVolumnTrack
|
|
804
|
-
this.#refreshSpeekVolumn = userAgentOption.refreshSpeekVolumn ?? this.#refreshSpeekVolumn
|
|
805
|
-
this.#refreshRequirementCheck = userAgentOption.refreshRequirementCheck ?? this.#refreshRequirementCheck
|
|
806
|
-
this.#refreshChat = userAgentOption.refreshChat ?? this.#refreshChat
|
|
807
|
-
this.#refreshChatErrorCallback = userAgentOption.refreshChatErrorCallback ?? this.#refreshChatErrorCallback
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
#initGatewayApiOption(userAgentOption: Partial<UserAgentOptions> = {}) {
|
|
811
|
-
this.#token = userAgentOption.token ?? this.#token
|
|
812
|
-
this.#tokenTimestamp = userAgentOption.tokenTimestamp ?? this.#tokenTimestamp
|
|
813
|
-
this.#tokenExpirationTime = userAgentOption.tokenExpirationTime ?? this.#tokenExpirationTime
|
|
814
|
-
if (this.#token && !this.#tokenTimestamp) {
|
|
815
|
-
this.#tokenTimestamp = Date.now()
|
|
816
|
-
}
|
|
817
|
-
this.#tokenCheck = userAgentOption.tokenCheck ?? this.#tokenCheck
|
|
818
|
-
this.#tokenOverdued = userAgentOption.tokenOverdued ?? this.#tokenOverdued
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
#initOpenApiOption(userAgentOption: Partial<UserAgentOptions> = {}) {
|
|
822
|
-
const {
|
|
823
|
-
agentId,
|
|
824
|
-
agentTag,
|
|
825
|
-
appKey,
|
|
826
|
-
appSecret,
|
|
827
|
-
openBaseUrl,
|
|
828
|
-
sg,
|
|
829
|
-
sgOpen,
|
|
830
|
-
sign,
|
|
831
|
-
timestamp
|
|
832
|
-
} = userAgentOption
|
|
833
|
-
this.#agentId = agentId ?? this.#agentId
|
|
834
|
-
this.#agentTag = agentTag ?? this.#agentTag
|
|
835
|
-
this.#appKey = appKey ?? this.#appKey
|
|
836
|
-
this.#appSecret = appSecret ?? this.#appSecret
|
|
837
|
-
this.#openBaseUrl = openBaseUrl ?? this.#openBaseUrl
|
|
838
|
-
this.#sg = sg ?? this.#sg
|
|
839
|
-
this.#sgOpen = sgOpen ?? this.#sgOpen
|
|
840
|
-
this.#sign= sign ?? this.#sign
|
|
841
|
-
this.#timestamp = timestamp ?? this.#timestamp
|
|
842
|
-
this.#signExpirationTime = userAgentOption.signExpirationTime ?? this.#signExpirationTime
|
|
843
|
-
this.#signCheck = userAgentOption.signCheck ?? this.#signCheck
|
|
844
|
-
this.#signOverdued = userAgentOption.signOverdued ?? this.#signOverdued
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
/**
|
|
848
|
-
* 初始化扩展header配置
|
|
849
|
-
* @param { UserAgentOptions } userAgentOption
|
|
850
|
-
* @private
|
|
851
|
-
*/
|
|
852
|
-
#initExtraHeaders (userAgentOption: Partial<UserAgentOptions>) {
|
|
853
|
-
if (userAgentOption.sipHeaders) {
|
|
854
|
-
this.#extraHeaders = userAgentOption.sipHeaders
|
|
855
|
-
delete userAgentOption.sipHeaders
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
public getDeviceId = async () => {
|
|
860
|
-
if (this.#visitorId) return this.#visitorId
|
|
861
|
-
const fp = await FingerprintJS.load()
|
|
862
|
-
const result = await fp.get()
|
|
863
|
-
this.#visitorId = result.visitorId
|
|
864
|
-
return this.#visitorId
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
#meetTheOpenApiRequirement() {
|
|
868
|
-
if ((this.#agentId || this.#agentTag) && ((this.#appKey && this.#appSecret) || (this.#sign && this.#timestamp))) {
|
|
869
|
-
return true
|
|
870
|
-
}
|
|
871
|
-
return false
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
refreshSign(sign: string) {
|
|
875
|
-
this.#sign = sign
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
#openApiRequirementAssign = async () => {
|
|
879
|
-
if (this.#meetTheOpenApiRequirement() &&
|
|
880
|
-
(!this.#userAgentOption.authorizationUsername ||
|
|
881
|
-
!this.#userAgentOption.uri ||
|
|
882
|
-
!this.#userAgentOption.authorizationPassword ||
|
|
883
|
-
!this.#userAgentOption.transportOptions.server
|
|
884
|
-
)
|
|
885
|
-
) {
|
|
886
|
-
const angentData = await this.getAgentInfo()
|
|
887
|
-
if (angentData.code == 200) {
|
|
888
|
-
const angentInfo = angentData.data
|
|
889
|
-
this.#userAgentOption.authorizationUsername = angentInfo.agentExtension
|
|
890
|
-
this.#userAgentOption.authorizationPassword = angentInfo.extensionPwd
|
|
891
|
-
// @ts-ignore
|
|
892
|
-
this.#userAgentOption.uri = UserAgent.makeURI(`sip:${angentInfo.agentExtension}@${angentInfo.wsRegisterAddress}`)
|
|
893
|
-
this.#userAgentOption.transportOptions.server = `${angentInfo.wsProtocol}://${angentInfo.wsRegisterAddress}`
|
|
894
|
-
this.#agentId = angentInfo.agentId
|
|
895
|
-
this.#agentTag = angentInfo.agentTag
|
|
896
|
-
} else {
|
|
897
|
-
throw new Error(angentData)
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
#viaHostAssign = async () => {
|
|
903
|
-
if (!this.#userAgentOption.viaHost) {
|
|
904
|
-
this.#userAgentOption.viaHost = `${await this.getDeviceId()}.sip`
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
/**
|
|
909
|
-
* 签入
|
|
910
|
-
* @param config 配置
|
|
911
|
-
* @param { (path: keyof UserAgentStatusKey, value: boolean) => void } config.refresh 配置
|
|
912
|
-
* @param { import('sip.js/lib/api/registerer-options').RegistererOptions } config.registererOptions new Registerer Options
|
|
913
|
-
* @param { import('sip.js/lib/api/registerer-register-options').RegistererRegisterOptions } config.registererRegisterOptions Registerer Instance register Options
|
|
914
|
-
* @param { string[] } options.extraHeaders sip custom header
|
|
915
|
-
* @param { import('sip.js/lib/api/user-agent-delegate').UserAgentDelegate } event 事件
|
|
916
|
-
*/
|
|
917
|
-
public async prepareUserAgent (config?: PrepareUserAgentOptions, event?: UserAgentDelegate) {
|
|
918
|
-
const {
|
|
919
|
-
refresh,
|
|
920
|
-
registererOptions,
|
|
921
|
-
registererRegisterOptions,
|
|
922
|
-
extraHeaders,
|
|
923
|
-
authorizationUsername,
|
|
924
|
-
authorizationPassword,
|
|
925
|
-
uri,
|
|
926
|
-
contactName,
|
|
927
|
-
transportOptions,
|
|
928
|
-
} = (config || {})
|
|
929
|
-
await this.dispose()
|
|
930
|
-
// @ts-ignore
|
|
931
|
-
this.#userAgentOption.uri = uri ?? this.#userAgentOption.uri
|
|
932
|
-
this.#userAgentOption.authorizationUsername = authorizationUsername ?? this.#userAgentOption.authorizationUsername
|
|
933
|
-
this.#userAgentOption.authorizationPassword = authorizationPassword ?? this.#userAgentOption.authorizationPassword
|
|
934
|
-
this.#userAgentOption.contactName = contactName ?? authorizationUsername ?? this.#userAgentOption.contactName
|
|
935
|
-
if (transportOptions) {
|
|
936
|
-
this.#userAgentOption.transportOptions = {
|
|
937
|
-
...this.#userAgentOption.transportOptions,
|
|
938
|
-
...transportOptions
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
try {
|
|
942
|
-
// @ts-ignore
|
|
943
|
-
this.#initOpenApiOption(config)
|
|
944
|
-
await this.#openApiRequirementAssign()
|
|
945
|
-
await this.#viaHostAssign()
|
|
946
|
-
await this.#connectUserAgent({ refresh })
|
|
947
|
-
if (event) {
|
|
948
|
-
this.#bindUserAgent(event)
|
|
949
|
-
}
|
|
950
|
-
const userAgent: InstanceType<typeof UserAgent> = await this.#registerUserAgent({
|
|
951
|
-
registererOptions,
|
|
952
|
-
registererRegisterOptions,
|
|
953
|
-
extraHeaders
|
|
954
|
-
})
|
|
955
|
-
return userAgent
|
|
956
|
-
} catch (e) {
|
|
957
|
-
await this.dispose()
|
|
958
|
-
throw e
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
/**
|
|
963
|
-
* 签出
|
|
964
|
-
*/
|
|
965
|
-
public async dispose () {
|
|
966
|
-
try {
|
|
967
|
-
this.#clearStream()
|
|
968
|
-
this.#clearSenders()
|
|
969
|
-
this.#clearReceivers()
|
|
970
|
-
this.#clearPeerConnection()
|
|
971
|
-
this.#clearSessionDescriptionHandler()
|
|
972
|
-
this.#clearAllTimer()
|
|
973
|
-
await this.#clearUserAgent()
|
|
974
|
-
this.#clearUserAgentStatus()
|
|
975
|
-
this.#stopTrackVolume()
|
|
976
|
-
this.#businessAttribute = {}
|
|
977
|
-
this.#pushData = {}
|
|
978
|
-
} catch (e) {
|
|
979
|
-
this.#clearUserAgentStatus()
|
|
980
|
-
console.log('sip dispose with error', e)
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
/**
|
|
985
|
-
* sip协议通讯
|
|
986
|
-
* @private
|
|
987
|
-
* @param headers
|
|
988
|
-
*/
|
|
989
|
-
#syncSipMessage = (headers: Record<string, any>) => {
|
|
990
|
-
this.#businessAttribute.popWindow = headers['X-Popwindow']?.[0].raw
|
|
991
|
-
this.#businessAttribute.afterTransferLabour = headers['X-Aftertransferlabour']?.[0].raw
|
|
992
|
-
this.#businessAttribute.callId = headers['X-94callid']?.[0].raw
|
|
993
|
-
this.#businessAttribute.cid = headers['X-Cid']?.[0].raw
|
|
994
|
-
this.#businessAttribute.numberId = headers['X-Numberid']?.[0].raw
|
|
995
|
-
this.#businessAttribute.taskId = headers['X-Taskid']?.[0].raw
|
|
996
|
-
this.#businessAttribute.voiceType = headers['X-Voicetype']?.[0].raw
|
|
997
|
-
this.#businessAttribute.processTime = headers['X-Processtime']?.[0].raw
|
|
998
|
-
this.#businessAttribute.userPhone = headers['X-Userphone']?.[0].raw
|
|
999
|
-
this.#businessAttribute.autoAnswer = headers['X-94autoanswer']?.[0].raw
|
|
1000
|
-
this.#businessAttribute.nodeTitle = headers['X-Nodetitle']?.[0].raw.split('-').map((item: number) => String.fromCharCode(item)).join('')
|
|
1001
|
-
this.#businessAttribute.taskName = headers['X-Taskname']?.[0].raw.split('-').map((item: number) => String.fromCharCode(item)).join('')
|
|
1002
|
-
this.#businessAttribute.templateTitle = headers['X-Templatetitle']?.[0].raw.split('-').map((item: number) => String.fromCharCode(item)).join('')
|
|
1003
|
-
this.#businessAttribute.phoneNumber = headers['X-Phonenumber']?.[0].raw || headers.From?.[0].parsed.uri.normal.user
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
/**
|
|
1007
|
-
* 忽略
|
|
1008
|
-
* @param { import('sip.js/lib/api/invitation-reject-options').InvitationRejectOptions } } options 配置
|
|
1009
|
-
*/
|
|
1010
|
-
public async ignoreInvite (options: InvitationRejectOptions = {}) {
|
|
1011
|
-
this.#userAgentStatus.incomingStatus = false
|
|
1012
|
-
await this.getCurrentInvitation()!.reject(options)
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
/**
|
|
1016
|
-
* 接听
|
|
1017
|
-
* @param { InvitationAcceptOptions } options 配置
|
|
1018
|
-
*/
|
|
1019
|
-
public async acceptInvite (options: InvitationAcceptOptions = {}) {
|
|
1020
|
-
try {
|
|
1021
|
-
// await requestMicroPhonePermission()
|
|
1022
|
-
await new Promise((resolve, reject) => {
|
|
1023
|
-
const fn = async () => {
|
|
1024
|
-
try {
|
|
1025
|
-
const onAck = options?.onAck
|
|
1026
|
-
const onAckTimeout = options?.onAckTimeout
|
|
1027
|
-
delete options?.onAck
|
|
1028
|
-
delete options?.onAckTimeout
|
|
1029
|
-
await this.getCurrentInvitation()!.accept({
|
|
1030
|
-
sessionDescriptionHandlerOptions: {
|
|
1031
|
-
constraints: {
|
|
1032
|
-
audio: true,
|
|
1033
|
-
video: false
|
|
1034
|
-
}
|
|
1035
|
-
},
|
|
1036
|
-
onAck (ackRequest: { message: string }) {
|
|
1037
|
-
resolve(ackRequest)
|
|
1038
|
-
onAck?.()
|
|
1039
|
-
},
|
|
1040
|
-
onAckTimeout () {
|
|
1041
|
-
onAckTimeout?.()
|
|
1042
|
-
reject(new Error('接听响应超时'))
|
|
1043
|
-
},
|
|
1044
|
-
...(options || {})
|
|
1045
|
-
})
|
|
1046
|
-
} catch (e) {
|
|
1047
|
-
reject(e)
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
fn()
|
|
1051
|
-
})
|
|
1052
|
-
if (this.#userAgentStatus.incomingStatus) { // 有可能在接听中服务端下发了cancel,然后又发了200给服务端,服务端会成功响应ack,导致这时候会挂断后又唤起接听成功
|
|
1053
|
-
this.#userAgentStatus.incomingStatus = false
|
|
1054
|
-
this.#userAgentStatus.answerStatus = true
|
|
1055
|
-
}
|
|
1056
|
-
} catch (e) {
|
|
1057
|
-
this.#userAgentStatus.incomingStatus = false
|
|
1058
|
-
throw e
|
|
1059
|
-
}
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
/**
|
|
1063
|
-
* 挂断
|
|
1064
|
-
* @param options 配置
|
|
1065
|
-
* @param { import('sip.js/lib/api/invitation-reject-options').InvitationRejectOptions } options.rejectOptions 当会话尚在建立中,配置挂断请求
|
|
1066
|
-
* @param { import('sip.js/lib/api/session-bye-options').SessionByeOptions } options.byeOptions 当会话已建立,配置挂断请求
|
|
1067
|
-
* @param { string[] } options.extraHeaders sip自定义头
|
|
1068
|
-
*/
|
|
1069
|
-
public async hangUpInvite (options?: HandUpInviteOption): Promise<boolean | IncomingResponse> {
|
|
1070
|
-
const {
|
|
1071
|
-
rejectOptions,
|
|
1072
|
-
byeOptions,
|
|
1073
|
-
extraHeaders,
|
|
1074
|
-
scoutResponse
|
|
1075
|
-
} = (options || {})
|
|
1076
|
-
return new Promise(async (resolve, reject) => {
|
|
1077
|
-
try {
|
|
1078
|
-
const byeOption: Record<string, any> = byeOptions || {}
|
|
1079
|
-
const onAccept = byeOption.requestDelegate?.onAccept
|
|
1080
|
-
const onReject = byeOption.requestDelegate?.onReject
|
|
1081
|
-
delete byeOption.requestDelegate?.onAccept
|
|
1082
|
-
delete byeOption.requestDelegate?.onReject
|
|
1083
|
-
const requestDelegate = this.#extendFreeDelegate({
|
|
1084
|
-
onAccept (response: IncomingResponse) {
|
|
1085
|
-
console.log('sip bye success')
|
|
1086
|
-
resolve(response)
|
|
1087
|
-
onAccept?.(response)
|
|
1088
|
-
},
|
|
1089
|
-
onReject (response: IncomingResponse) {
|
|
1090
|
-
onReject?.(response)
|
|
1091
|
-
reject(new Error('sip bye fail with code ' + response.message.statusCode))
|
|
1092
|
-
}
|
|
1093
|
-
}, byeOption.requestDelegate as Record<string, Function>)
|
|
1094
|
-
delete byeOption.requestDelegate
|
|
1095
|
-
switch (this.#currentInvitation?.state) {
|
|
1096
|
-
case SessionState.Initial:
|
|
1097
|
-
case SessionState.Establishing:
|
|
1098
|
-
// if (this.#currentInvitation instanceof Inviter) {
|
|
1099
|
-
// // An unestablished outgoing session
|
|
1100
|
-
// this.#currentInvitation!.cancel()
|
|
1101
|
-
// } else {
|
|
1102
|
-
// An unestablished incoming session
|
|
1103
|
-
await this.#currentInvitation!.reject({
|
|
1104
|
-
extraHeaders,
|
|
1105
|
-
...(rejectOptions || {})
|
|
1106
|
-
})
|
|
1107
|
-
resolve(true)
|
|
1108
|
-
// }
|
|
1109
|
-
break
|
|
1110
|
-
case SessionState.Established:
|
|
1111
|
-
// An established session
|
|
1112
|
-
await this.#currentInvitation!.bye({
|
|
1113
|
-
requestDelegate,
|
|
1114
|
-
requestOptions: {
|
|
1115
|
-
extraHeaders
|
|
1116
|
-
},
|
|
1117
|
-
...byeOption
|
|
1118
|
-
})
|
|
1119
|
-
if (!scoutResponse) {
|
|
1120
|
-
resolve(true)
|
|
1121
|
-
}
|
|
1122
|
-
break
|
|
1123
|
-
case SessionState.Terminating:
|
|
1124
|
-
case SessionState.Terminated:
|
|
1125
|
-
resolve(true)
|
|
1126
|
-
// Cannot terminate a session that is already terminated
|
|
1127
|
-
break
|
|
1128
|
-
}
|
|
1129
|
-
} catch (e) {
|
|
1130
|
-
reject(e)
|
|
1131
|
-
} finally {
|
|
1132
|
-
this.#userAgentStatus.incomingStatus = false // 可能用户主动挂断,非坐席手动挂断,这个时候自己被动挂断也要把来电状态重置下
|
|
1133
|
-
this.#userAgentStatus.answerStatus = false
|
|
1134
|
-
}
|
|
1135
|
-
})
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
/**
|
|
1139
|
-
* 按*转人工
|
|
1140
|
-
* @param { import('sip.js/lib/api/session-info-options').SessionInfoOptions & {extraHeaders: string[]} } options 配置
|
|
1141
|
-
*/
|
|
1142
|
-
public async sendStarDtmf (options: SessionInfoOptions = {}): Promise<IncomingResponse> {
|
|
1143
|
-
return new Promise(async (resolve, reject) => {
|
|
1144
|
-
try {
|
|
1145
|
-
const option = (options || {})
|
|
1146
|
-
const { extraHeaders, requestDelegate } = option
|
|
1147
|
-
const delegate = this.#extendFreeDelegate({
|
|
1148
|
-
onAccept (response: IncomingResponse) {
|
|
1149
|
-
console.log('sip send dtmf Signal=* success')
|
|
1150
|
-
resolve(response)
|
|
1151
|
-
},
|
|
1152
|
-
onReject (response: IncomingResponse) {
|
|
1153
|
-
reject(new Error('sip send dtmf Signal=* fail with code ' + response.message.statusCode))
|
|
1154
|
-
}
|
|
1155
|
-
}, requestDelegate as Record<string, Function>)
|
|
1156
|
-
delete option.requestDelegate
|
|
1157
|
-
await this.getCurrentInvitation().info({
|
|
1158
|
-
requestOptions: {
|
|
1159
|
-
body: {
|
|
1160
|
-
contentDisposition: 'render',
|
|
1161
|
-
contentType: 'application/dtmf-relay',
|
|
1162
|
-
content: 'Signal=*\r\nDuration=100'
|
|
1163
|
-
},
|
|
1164
|
-
extraHeaders
|
|
1165
|
-
},
|
|
1166
|
-
requestDelegate: delegate,
|
|
1167
|
-
...option
|
|
1168
|
-
})
|
|
1169
|
-
this.#trackVolume()
|
|
1170
|
-
} catch (e) {
|
|
1171
|
-
reject(e)
|
|
1172
|
-
}
|
|
1173
|
-
})
|
|
1174
|
-
}
|
|
1175
|
-
|
|
1176
|
-
/**
|
|
1177
|
-
* 拨出
|
|
1178
|
-
* @param { import('sip.js/lib/api/inviter-invite-options').InviterInviteOptions & {extraHeaders?: string[]} } options 配置
|
|
1179
|
-
*/
|
|
1180
|
-
public async sendCurrentInviter (options: InviterInviteOptions = {}) {
|
|
1181
|
-
const { extraHeaders } = (options || {})
|
|
1182
|
-
// @ts-ignore
|
|
1183
|
-
await this.getCurrentInviter().invite({
|
|
1184
|
-
requestOptions: {
|
|
1185
|
-
extraHeaders
|
|
1186
|
-
},
|
|
1187
|
-
...options
|
|
1188
|
-
})
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
/**
|
|
1192
|
-
* 不接听远端声音
|
|
1193
|
-
*/
|
|
1194
|
-
public muteRemoteAudio () {
|
|
1195
|
-
this.#peerConnection!.getReceivers().forEach(item => {
|
|
1196
|
-
item.track.enabled = false
|
|
1197
|
-
})
|
|
1198
|
-
}
|
|
1199
|
-
|
|
1200
|
-
/**
|
|
1201
|
-
* 接听远端声音
|
|
1202
|
-
*/
|
|
1203
|
-
public unMuteRemoteAudio () {
|
|
1204
|
-
this.#peerConnection!.getReceivers().forEach(item => {
|
|
1205
|
-
item.track.enabled = true
|
|
1206
|
-
})
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
/**
|
|
1210
|
-
* 不传输本地声音到远端
|
|
1211
|
-
*/
|
|
1212
|
-
public muteLocalAudio () {
|
|
1213
|
-
this.#peerConnection!.getSenders().forEach(item => {
|
|
1214
|
-
item.track!.enabled = false
|
|
1215
|
-
})
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
/**
|
|
1219
|
-
* 传输本地声音到远端
|
|
1220
|
-
*/
|
|
1221
|
-
public unMuteLocalAudio () {
|
|
1222
|
-
this.#peerConnection!.getSenders().forEach(item => {
|
|
1223
|
-
item.track!.enabled = true
|
|
1224
|
-
})
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
/**
|
|
1228
|
-
* 获取代理实例状态
|
|
1229
|
-
*/
|
|
1230
|
-
public getUserAgentStatue () {
|
|
1231
|
-
return this.#userAgentStatus
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
/**
|
|
1235
|
-
* 获取代理实例
|
|
1236
|
-
*/
|
|
1237
|
-
public getUserAgent () {
|
|
1238
|
-
if (!this.#userAgent) {
|
|
1239
|
-
// @ts-ignore
|
|
1240
|
-
this.#userAgent = new UserAgent(this.#userAgentOption)
|
|
1241
|
-
}
|
|
1242
|
-
return this.#userAgent
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
/**
|
|
1246
|
-
* 获取会话实例
|
|
1247
|
-
*/
|
|
1248
|
-
public getSessionDescriptionHandler () {
|
|
1249
|
-
return this.#sessionDescriptionHandler
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
/**
|
|
1253
|
-
* 获取peerConnection
|
|
1254
|
-
*/
|
|
1255
|
-
public getPeerConnection () {
|
|
1256
|
-
return this.#peerConnection
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
/**
|
|
1260
|
-
* 获取senders
|
|
1261
|
-
*/
|
|
1262
|
-
public getSenders () {
|
|
1263
|
-
return this.#senders
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
/**
|
|
1267
|
-
* 获取receivers
|
|
1268
|
-
*/
|
|
1269
|
-
public getReceivers () {
|
|
1270
|
-
return this.#receivers
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
/**
|
|
1274
|
-
* 获取流对象
|
|
1275
|
-
*/
|
|
1276
|
-
public getStream () {
|
|
1277
|
-
if (!this.#stream) {
|
|
1278
|
-
this.#stream = new MediaStream()
|
|
1279
|
-
}
|
|
1280
|
-
this.#receivers.forEach((receiver) => {
|
|
1281
|
-
if (receiver.track) {
|
|
1282
|
-
this.#stream!.addTrack(receiver.track)
|
|
1283
|
-
}
|
|
1284
|
-
})
|
|
1285
|
-
return this.#stream
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
/**
|
|
1289
|
-
* 拨出实例
|
|
1290
|
-
*/
|
|
1291
|
-
public getCurrentInviter () {
|
|
1292
|
-
if (!this.#currentInviter) {
|
|
1293
|
-
throw new Error('No currentInviter, please call createCurrentInviter')
|
|
1294
|
-
}
|
|
1295
|
-
return this.#currentInviter
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
/**
|
|
1299
|
-
* 接听实例
|
|
1300
|
-
*/
|
|
1301
|
-
|
|
1302
|
-
public getCurrentInvitation () {
|
|
1303
|
-
return this.#currentInvitation
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
/**
|
|
1307
|
-
* 增加状态响应式
|
|
1308
|
-
* @param {{ refresh: (path: keyof UserAgentStatusKey, value: boolean) => void }} config
|
|
1309
|
-
* @private
|
|
1310
|
-
*/
|
|
1311
|
-
#initStatus (config: { refresh: (path: UserAgentStatusKey, value: boolean) => void }) {
|
|
1312
|
-
// @ts-ignore
|
|
1313
|
-
this.#userAgentStatus = onChange(JSON.parse(JSON.stringify(userAgentStatus)), typeof config.refresh === 'function' ? config.refresh : this.#empty)
|
|
1314
|
-
}
|
|
1315
|
-
|
|
1316
|
-
/**
|
|
1317
|
-
* 初始换KeepAlive配置
|
|
1318
|
-
* @param { UserAgentOptions } userAgentOption
|
|
1319
|
-
* @private
|
|
1320
|
-
*/
|
|
1321
|
-
#initKeepAliveOptions (userAgentOption: Partial<UserAgentOptions>) {
|
|
1322
|
-
this.#reconnectionAttempts = userAgentOption.reconnectionAttempts ?? userAgentOption?.transportOptions?.maxReconnectionAttempts ?? this.#reconnectionAttempts
|
|
1323
|
-
this.#reconnectionInterval = userAgentOption.reconnectionInterval ?? userAgentOption.reconnectionDelay ?? userAgentOption?.transportOptions?.reconnectionTimeout ?? this.#reconnectionInterval
|
|
1324
|
-
this.#keepAliveInterval = userAgentOption.transportOptions?.keepAliveInterval ?? this.#keepAliveInterval
|
|
1325
|
-
this.#keepAliveDebounce = userAgentOption.transportOptions?.keepAliveDebounce ?? this.#keepAliveDebounce
|
|
1326
|
-
this.#registerInterval = userAgentOption.registerInterval ?? this.#registerInterval
|
|
1327
|
-
this.#optionsPingInterval = userAgentOption.optionsPingInterval ?? this.#optionsPingInterval
|
|
1328
|
-
this.#optionsPingAttempts = userAgentOption.optionsPingAttempts ?? this.#optionsPingAttempts
|
|
1329
|
-
this.#registerAttempts = userAgentOption.registerAttempts ?? this.#registerAttempts
|
|
1330
|
-
// window.addEventListener("online", this.#attemptReconnectionHandler);
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
#extendFreeDelegate (initDelegate: Record<string, Function>, extendDelegate?: Record<string, Function>) {
|
|
1334
|
-
if (!extendDelegate) return initDelegate
|
|
1335
|
-
Object.keys(extendDelegate).forEach((extendDelegateKey) => {
|
|
1336
|
-
if (extendDelegate[extendDelegateKey] && typeof extendDelegate[extendDelegateKey] === 'function') {
|
|
1337
|
-
if (initDelegate[extendDelegateKey]) {
|
|
1338
|
-
if (typeof initDelegate[extendDelegateKey] === 'function') {
|
|
1339
|
-
const tmp = initDelegate[extendDelegateKey]
|
|
1340
|
-
initDelegate[extendDelegateKey] = async (param: any) => {
|
|
1341
|
-
await extendDelegate[extendDelegateKey](param)
|
|
1342
|
-
await tmp(param)
|
|
1343
|
-
}
|
|
1344
|
-
}
|
|
1345
|
-
} else {
|
|
1346
|
-
initDelegate[extendDelegateKey] = extendDelegate[extendDelegateKey]
|
|
1347
|
-
}
|
|
1348
|
-
}
|
|
1349
|
-
})
|
|
1350
|
-
return initDelegate
|
|
1351
|
-
}
|
|
1352
|
-
|
|
1353
|
-
/**
|
|
1354
|
-
* 初始化sip配置
|
|
1355
|
-
* @param { UserAgentOptions } userAgentOption
|
|
1356
|
-
* @private
|
|
1357
|
-
*/
|
|
1358
|
-
#getUserAgentOption (userAgentOption: Partial<UserAgentOptions>) {
|
|
1359
|
-
const initUserAgentOption: Partial<UserAgentOptions> = {
|
|
1360
|
-
delegate: {
|
|
1361
|
-
onConnect: async () => {
|
|
1362
|
-
console.log('sip connected')
|
|
1363
|
-
this.#optionsPingStart()
|
|
1364
|
-
},
|
|
1365
|
-
onInvite: (invitation: Invitation) => {
|
|
1366
|
-
// @ts-ignore
|
|
1367
|
-
this.#syncSipMessage(invitation.incomingInviteRequest.message.headers)
|
|
1368
|
-
this.refreshCallChatInfo()
|
|
1369
|
-
this.#clearCurrentInvitation()
|
|
1370
|
-
this.#currentInvitation = invitation
|
|
1371
|
-
this.#userAgentStatus.incomingStatus = true
|
|
1372
|
-
invitation.stateChange.addListener(this.#receivedCall)
|
|
1373
|
-
},
|
|
1374
|
-
onDisconnect: async (error?: Error) => {
|
|
1375
|
-
console.log('sip disconnected')
|
|
1376
|
-
let optionsPingFailure = false
|
|
1377
|
-
if (this.#optionsPingInterval > 0) {
|
|
1378
|
-
optionsPingFailure = this.#optionsPingFailure
|
|
1379
|
-
this.#optionsPingStop()
|
|
1380
|
-
}
|
|
1381
|
-
await this.#unRegisterer()
|
|
1382
|
-
// Only attempt to reconnect if network/server dropped the connection (if there is an error)
|
|
1383
|
-
if (error || optionsPingFailure) {
|
|
1384
|
-
this.#attemptReconnection()
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
if (userAgentOption.delegate) {
|
|
1390
|
-
Object.keys(userAgentOption.delegate).forEach((userAgentDelegateKey) => {
|
|
1391
|
-
const key = userAgentDelegateKey as UserAgentDelegateKey
|
|
1392
|
-
if (userAgentOption.delegate![key] && typeof userAgentOption.delegate![key] === 'function') {
|
|
1393
|
-
const tmp = userAgentOption.delegate![key] as Function
|
|
1394
|
-
// @ts-ignore
|
|
1395
|
-
userAgentOption.delegate[key] = async (invitation: Invitation) => {
|
|
1396
|
-
// @ts-ignore
|
|
1397
|
-
initUserAgentOption.delegate![key]?.(invitation)
|
|
1398
|
-
await tmp!(invitation)
|
|
1399
|
-
}
|
|
1400
|
-
}
|
|
1401
|
-
})
|
|
1402
|
-
} else {
|
|
1403
|
-
userAgentOption.delegate = initUserAgentOption.delegate
|
|
1404
|
-
}
|
|
1405
|
-
/** 关闭transport层的重连 */
|
|
1406
|
-
userAgentOption.reconnectionAttempts = 0
|
|
1407
|
-
userAgentOption.transportOptions = {
|
|
1408
|
-
...(userAgentOption.transportOptions || {}),
|
|
1409
|
-
keepAliveInterval: this.#keepAliveInterval,
|
|
1410
|
-
keepAliveDebounce: this.#keepAliveDebounce
|
|
1411
|
-
}
|
|
1412
|
-
if (typeof userAgentOption.sessionDescriptionHandlerFactoryOptions === 'undefined') {
|
|
1413
|
-
userAgentOption.sessionDescriptionHandlerFactoryOptions = {
|
|
1414
|
-
iceGatheringTimeout: 2000,
|
|
1415
|
-
peerConnectionConfiguration: {
|
|
1416
|
-
iceServers: []
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
return {
|
|
1421
|
-
...userAgentDefault,
|
|
1422
|
-
...userAgentOption
|
|
1423
|
-
} as UserAgentOptions
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
/**
|
|
1427
|
-
* 清除接听实例
|
|
1428
|
-
* @private
|
|
1429
|
-
*/
|
|
1430
|
-
|
|
1431
|
-
#clearCurrentInvitation () {
|
|
1432
|
-
if (this.#currentInvitation) {
|
|
1433
|
-
this.#currentInvitation.dispose()
|
|
1434
|
-
this.#currentInvitation = undefined
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
|
|
1438
|
-
/**
|
|
1439
|
-
* 清除拨出实例
|
|
1440
|
-
* @private
|
|
1441
|
-
*/
|
|
1442
|
-
#clearCurrentInviter () {
|
|
1443
|
-
if (this.#currentInviter) {
|
|
1444
|
-
this.#currentInviter.dispose()
|
|
1445
|
-
this.#currentInviter = undefined
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
/**
|
|
1450
|
-
* 清除用户代理实例
|
|
1451
|
-
* @private
|
|
1452
|
-
*/
|
|
1453
|
-
async #clearUserAgent () {
|
|
1454
|
-
if (this.#userAgent) {
|
|
1455
|
-
await this.#userAgent.stop()
|
|
1456
|
-
this.#userAgent = undefined
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
/**
|
|
1461
|
-
* 清除流对象
|
|
1462
|
-
* @private
|
|
1463
|
-
*/
|
|
1464
|
-
#clearStream () {
|
|
1465
|
-
if (this.#stream) {
|
|
1466
|
-
this.#stream.getTracks().forEach(track => {
|
|
1467
|
-
track.stop()
|
|
1468
|
-
})
|
|
1469
|
-
this.#stream = undefined
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
/**
|
|
1474
|
-
* 重置用户代理状态
|
|
1475
|
-
* @private
|
|
1476
|
-
*/
|
|
1477
|
-
#clearUserAgentStatus () {
|
|
1478
|
-
Object.keys(userAgentStatus).forEach(userAgentStatusKey => {
|
|
1479
|
-
const key = userAgentStatusKey as UserAgentStatusKey
|
|
1480
|
-
this.#userAgentStatus[key] = userAgentStatus[key]
|
|
1481
|
-
})
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
/**
|
|
1485
|
-
* 清除会话实例对象
|
|
1486
|
-
* @private
|
|
1487
|
-
*/
|
|
1488
|
-
#clearSessionDescriptionHandler () {
|
|
1489
|
-
if (this.#sessionDescriptionHandler) {
|
|
1490
|
-
this.#sessionDescriptionHandler.close()
|
|
1491
|
-
this.#sessionDescriptionHandler = undefined
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
/**
|
|
1496
|
-
* 清除peerConnection
|
|
1497
|
-
* @private
|
|
1498
|
-
*/
|
|
1499
|
-
#clearPeerConnection () {
|
|
1500
|
-
if (this.#peerConnection) {
|
|
1501
|
-
this.#peerConnection.close()
|
|
1502
|
-
this.#peerConnection = undefined
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
/**
|
|
1507
|
-
* 清除senders
|
|
1508
|
-
* @private
|
|
1509
|
-
*/
|
|
1510
|
-
#clearSenders () {
|
|
1511
|
-
this.#senders = []
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
/**
|
|
1515
|
-
* 清除receivers
|
|
1516
|
-
* @private
|
|
1517
|
-
*/
|
|
1518
|
-
#clearReceivers () {
|
|
1519
|
-
this.#receivers = []
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
/**
|
|
1523
|
-
* 收集webrtc对象实例
|
|
1524
|
-
* @private
|
|
1525
|
-
*/
|
|
1526
|
-
#setupSession () {
|
|
1527
|
-
const invitation = this.getCurrentInvitation()
|
|
1528
|
-
const {
|
|
1529
|
-
sessionDescriptionHandler
|
|
1530
|
-
} = invitation!
|
|
1531
|
-
this.#clearPeerConnection()
|
|
1532
|
-
this.#clearSessionDescriptionHandler()
|
|
1533
|
-
this.#clearSenders()
|
|
1534
|
-
this.#clearReceivers()
|
|
1535
|
-
this.#sessionDescriptionHandler = sessionDescriptionHandler
|
|
1536
|
-
const sessionDescriptionHandlerType = sessionDescriptionHandler as Record<string, any>
|
|
1537
|
-
this.#peerConnection = sessionDescriptionHandlerType.peerConnection as RTCPeerConnection
|
|
1538
|
-
this.#senders = this.#peerConnection.getSenders()
|
|
1539
|
-
this.#receivers = this.#peerConnection.getReceivers()
|
|
1540
|
-
if (this.getFastOutboundCall() || (!this.getSitOutboundCall() && this.notNeedSendStarDtmf())) {
|
|
1541
|
-
this.#trackVolume()
|
|
1542
|
-
}
|
|
1543
|
-
// this.muteLocalAudio()
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
/**
|
|
1547
|
-
* 跟踪会话建立
|
|
1548
|
-
* @param { SessionState } state
|
|
1549
|
-
*/
|
|
1550
|
-
#receivedCall = (state: SessionState) => {
|
|
1551
|
-
switch (state) {
|
|
1552
|
-
case SessionState.Initial:
|
|
1553
|
-
break
|
|
1554
|
-
case SessionState.Establishing:
|
|
1555
|
-
break
|
|
1556
|
-
case SessionState.Established:
|
|
1557
|
-
this.#setupSession()
|
|
1558
|
-
break
|
|
1559
|
-
case SessionState.Terminating:
|
|
1560
|
-
// fall through
|
|
1561
|
-
case SessionState.Terminated:
|
|
1562
|
-
let timer: ReturnType<typeof setTimeout> | undefined = setTimeout(() => {
|
|
1563
|
-
clearTimeout(timer)
|
|
1564
|
-
timer = undefined
|
|
1565
|
-
this.#userAgentStatus.incomingStatus = false
|
|
1566
|
-
}, 10)
|
|
1567
|
-
this.#stopTrackVolume()
|
|
1568
|
-
break
|
|
1569
|
-
default:
|
|
1570
|
-
throw new Error('Unknown session state.')
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
/**
|
|
1575
|
-
* 生成拨出实例
|
|
1576
|
-
* @param { string } uri 拨出地址
|
|
1577
|
-
* @private
|
|
1578
|
-
*/
|
|
1579
|
-
async #createCurrentInviter (uri: string) {
|
|
1580
|
-
const target = UserAgent.makeURI(uri)
|
|
1581
|
-
if (!target) {
|
|
1582
|
-
throw new Error('Failed to create target URI.')
|
|
1583
|
-
}
|
|
1584
|
-
this.#currentInviter = new Inviter(this.getUserAgent(), target, {
|
|
1585
|
-
sessionDescriptionHandlerOptions: {
|
|
1586
|
-
constraints: {
|
|
1587
|
-
audio: true,
|
|
1588
|
-
video: false
|
|
1589
|
-
}
|
|
1590
|
-
},
|
|
1591
|
-
})
|
|
1592
|
-
return this.#currentInviter
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
/**
|
|
1596
|
-
* 取消注册
|
|
1597
|
-
* @private
|
|
1598
|
-
* @param { import('sip.js/lib/api/registerer-unregister-options').RegistererUnregisterOptions } options 配置
|
|
1599
|
-
*/
|
|
1600
|
-
async #unRegisterer (options: RegistererUnregisterOptions = {}) {
|
|
1601
|
-
if (this.#registerer) {
|
|
1602
|
-
console.log('sip due to disconnection, unregistered')
|
|
1603
|
-
return new Promise(async (resolve, reject) => {
|
|
1604
|
-
try {
|
|
1605
|
-
const option = options || {}
|
|
1606
|
-
const requestDelegate = this.#extendFreeDelegate({
|
|
1607
|
-
onAccept: (response: IncomingResponse) => {
|
|
1608
|
-
console.log('sip unregister successed')
|
|
1609
|
-
resolve(response)
|
|
1610
|
-
// 由于业务对接过程涉及多系注册信息同步,状态同步有一定延迟,为了不让用户乱操作,对外保留注册状态
|
|
1611
|
-
// this.#userAgentStatus.registerStatus = false
|
|
1612
|
-
},
|
|
1613
|
-
onReject (response: IncomingResponse) {
|
|
1614
|
-
reject(new Error('sip unregister fail with code ' + response.message.statusCode))
|
|
1615
|
-
}
|
|
1616
|
-
}, option.requestDelegate as Record<string, Function>)
|
|
1617
|
-
delete options.requestDelegate
|
|
1618
|
-
await this.#registerer.unregister({
|
|
1619
|
-
// @ts-ignore
|
|
1620
|
-
requestDelegate,
|
|
1621
|
-
...options
|
|
1622
|
-
})
|
|
1623
|
-
} catch (error) {
|
|
1624
|
-
reject(error)
|
|
1625
|
-
}
|
|
1626
|
-
})
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
/**
|
|
1631
|
-
* 监听ping成功
|
|
1632
|
-
* @param requestURI
|
|
1633
|
-
* @param fromURI
|
|
1634
|
-
* @param toURI
|
|
1635
|
-
* @private
|
|
1636
|
-
*/
|
|
1637
|
-
#onPingSuccess = (requestURI: URI, fromURI: URI, toURI: URI) => {
|
|
1638
|
-
if (this.#optionsPingInterval) {
|
|
1639
|
-
this.#optionsPingFailure = false
|
|
1640
|
-
this.#userAgentStatus.reconnectStatus = false
|
|
1641
|
-
if (this.#optionsPingRunning) {
|
|
1642
|
-
this.#optionsPingRunning = false
|
|
1643
|
-
this.#optionsPingRun(requestURI, fromURI, toURI)
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
/**
|
|
1648
|
-
* 监听ping失败
|
|
1649
|
-
* @private
|
|
1650
|
-
*/
|
|
1651
|
-
#onPingFailure = async () => {
|
|
1652
|
-
if (this.#optionsPingInterval) {
|
|
1653
|
-
this.#optionsPingFailure = true
|
|
1654
|
-
this.#optionsPingRunning = false
|
|
1655
|
-
await this.getUserAgent().transport.disconnect().catch((error) => console.error(error))
|
|
1656
|
-
console.log(`sip transport disconnect`)
|
|
1657
|
-
try {
|
|
1658
|
-
await this.#unRegisterer()
|
|
1659
|
-
} catch {}
|
|
1660
|
-
this.#optionsPingStop()
|
|
1661
|
-
this.#attemptReconnection()
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
/**
|
|
1665
|
-
* 启动ping keeyalive
|
|
1666
|
-
* @private
|
|
1667
|
-
*/
|
|
1668
|
-
#optionsPingStart = () => {
|
|
1669
|
-
if (this.#optionsPingInterval) {
|
|
1670
|
-
// @ts-ignore
|
|
1671
|
-
const fromURI = this.#userAgentOption.uri.clone()
|
|
1672
|
-
// @ts-ignore
|
|
1673
|
-
const toURI = this.#userAgentOption.uri.clone()
|
|
1674
|
-
// @ts-ignore
|
|
1675
|
-
const requestURI = this.#userAgentOption.uri.clone()
|
|
1676
|
-
requestURI.user = undefined
|
|
1677
|
-
this.#optionsPingRun(requestURI, fromURI, toURI)
|
|
1678
|
-
}
|
|
1679
|
-
}
|
|
1680
|
-
/**
|
|
1681
|
-
* 发起ping
|
|
1682
|
-
* @param { URI } requestURI
|
|
1683
|
-
* @param { URI } fromURI
|
|
1684
|
-
* @param { URI } toURI
|
|
1685
|
-
* @private
|
|
1686
|
-
*/
|
|
1687
|
-
#optionsPingRun = (requestURI: URI, fromURI: URI, toURI: URI) => {
|
|
1688
|
-
if (this.#optionsPingInterval) {
|
|
1689
|
-
if (this.#optionsPingRunning) {
|
|
1690
|
-
return
|
|
1691
|
-
}
|
|
1692
|
-
this.#optionsPingRunning = true
|
|
1693
|
-
this.#optionsPingTimer = setTimeout(() => {
|
|
1694
|
-
this.#clearOptionsPingTimer()
|
|
1695
|
-
const core = this.getUserAgent().userAgentCore
|
|
1696
|
-
const message = core.makeOutgoingRequestMessage('OPTIONS', requestURI, fromURI, toURI, {})
|
|
1697
|
-
// @ts-ignore
|
|
1698
|
-
this.#optionsPingRequest = core.request(message, {
|
|
1699
|
-
onAccept: () => {
|
|
1700
|
-
console.log('sip ping ok')
|
|
1701
|
-
this.#optionsPingAttempt = 0
|
|
1702
|
-
this.#optionsPingRequest = undefined
|
|
1703
|
-
this.#onPingSuccess(requestURI, fromURI, toURI)
|
|
1704
|
-
},
|
|
1705
|
-
onReject: (response) => {
|
|
1706
|
-
this.#optionsPingRequest = undefined
|
|
1707
|
-
// - 408 Request Timeout (no response was received)
|
|
1708
|
-
// - 503 Service Unavailable (a transport layer error occured)
|
|
1709
|
-
// - 410 Unauthorized
|
|
1710
|
-
// - 484 Address incomplete
|
|
1711
|
-
// - 482 Loop detected
|
|
1712
|
-
if (response.message.statusCode === 408 || response.message.statusCode === 503) {
|
|
1713
|
-
console.log('sip ping error with code ' + response.message.statusCode)
|
|
1714
|
-
if (this.#optionsPingAttempts > 0 && this.#optionsPingAttempt >= this.#optionsPingAttempts) {
|
|
1715
|
-
console.log(`sip maximum ping attempts reached`)
|
|
1716
|
-
this.#onPingFailure()
|
|
1717
|
-
} else {
|
|
1718
|
-
if (this.#optionsPingAttempt > 0) {
|
|
1719
|
-
console.log(`sip ping retry ${this.#optionsPingAttempt} of ${this.#optionsPingAttempts} fail`)
|
|
1720
|
-
}
|
|
1721
|
-
this.#optionsPingAttempt++
|
|
1722
|
-
console.log(`sip ping retry ${this.#optionsPingAttempt} of ${this.#optionsPingAttempts}`)
|
|
1723
|
-
this.#onPingSuccess(requestURI, fromURI, toURI)
|
|
1724
|
-
}
|
|
1725
|
-
} else {
|
|
1726
|
-
this.#optionsPingAttempt = 0
|
|
1727
|
-
this.#onPingSuccess(requestURI, fromURI, toURI)
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
})
|
|
1731
|
-
}, this.#optionsPingInterval * 1000)
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
/**
|
|
1735
|
-
* 停止ping
|
|
1736
|
-
* @private
|
|
1737
|
-
*/
|
|
1738
|
-
#optionsPingStop = () => {
|
|
1739
|
-
if (this.#optionsPingInterval) {
|
|
1740
|
-
this.#optionsPingRunning = false
|
|
1741
|
-
this.#optionsPingFailure = false
|
|
1742
|
-
if (this.#optionsPingRequest) {
|
|
1743
|
-
try {
|
|
1744
|
-
this.#optionsPingRequest.dispose()
|
|
1745
|
-
} catch {
|
|
1746
|
-
}
|
|
1747
|
-
this.#optionsPingRequest = undefined
|
|
1748
|
-
}
|
|
1749
|
-
if (this.#optionsPingTimer) {
|
|
1750
|
-
this.#clearOptionsPingTimer()
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
/**
|
|
1756
|
-
* 清除keepalive机制定时
|
|
1757
|
-
* @private
|
|
1758
|
-
*/
|
|
1759
|
-
#clearAllTimer () {
|
|
1760
|
-
this.#clearRegisterTimer()
|
|
1761
|
-
this.#clearReconnectTimer()
|
|
1762
|
-
this.#clearOptionsPingTimer()
|
|
1763
|
-
this.#registerAttempting = false
|
|
1764
|
-
this.#reconnectionAttempting = false
|
|
1765
|
-
this.#optionsPingRunning = false
|
|
1766
|
-
}
|
|
1767
|
-
|
|
1768
|
-
/**
|
|
1769
|
-
* 清除注册定制器
|
|
1770
|
-
* @private
|
|
1771
|
-
*/
|
|
1772
|
-
#clearRegisterTimer () {
|
|
1773
|
-
clearTimeout(this.#registerTimer)
|
|
1774
|
-
this.#registerTimer = undefined
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
/**
|
|
1778
|
-
* 清除reconnect定制器
|
|
1779
|
-
* @private
|
|
1780
|
-
*/
|
|
1781
|
-
#clearReconnectTimer () {
|
|
1782
|
-
clearTimeout(this.#reconnectionTimer)
|
|
1783
|
-
this.#reconnectionTimer = undefined
|
|
1784
|
-
}
|
|
1785
|
-
|
|
1786
|
-
/**
|
|
1787
|
-
* 清除ping定制器
|
|
1788
|
-
* @private
|
|
1789
|
-
*/
|
|
1790
|
-
#clearOptionsPingTimer () {
|
|
1791
|
-
clearTimeout(this.#optionsPingTimer)
|
|
1792
|
-
this.#optionsPingTimer = undefined
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
/**
|
|
1796
|
-
* 重新注册
|
|
1797
|
-
* @private
|
|
1798
|
-
*/
|
|
1799
|
-
#attemptRegister = () => {
|
|
1800
|
-
if (this.#registerInterval) {
|
|
1801
|
-
if (this.#registerAttempting) {
|
|
1802
|
-
return
|
|
1803
|
-
}
|
|
1804
|
-
this.#registerAttempting = true
|
|
1805
|
-
this.#userAgentStatus.reconnectStatus = true
|
|
1806
|
-
this.#registerTimer = setTimeout(async () => {
|
|
1807
|
-
try {
|
|
1808
|
-
this.#clearRegisterTimer()
|
|
1809
|
-
if (this.#registerAttempt === 0) {
|
|
1810
|
-
console.log('sip reconnect success then register start')
|
|
1811
|
-
}
|
|
1812
|
-
await this.#registerer.register({
|
|
1813
|
-
requestDelegate: {
|
|
1814
|
-
// @ts-ignore
|
|
1815
|
-
onAccept: (response: IncomingResponse) => {
|
|
1816
|
-
console.log('sip reconnect success and register ok')
|
|
1817
|
-
this.#registerAttempt = 0
|
|
1818
|
-
this.#registerAttempting = false
|
|
1819
|
-
this.#userAgentStatus.registerStatus = true
|
|
1820
|
-
this.#userAgentStatus.reconnectStatus = false
|
|
1821
|
-
this.#optionsPingStart()
|
|
1822
|
-
},
|
|
1823
|
-
// @ts-ignore
|
|
1824
|
-
onReject: async (response: IncomingResponse) => {
|
|
1825
|
-
console.log('sip reconnect success but register fail with code ' + response.message.statusCode)
|
|
1826
|
-
this.#registerAttempting = false
|
|
1827
|
-
// if (response.message.statusCode === 408 || response.message.statusCode === 503) {
|
|
1828
|
-
if (this.#registerAttempts <= this.#registerAttempt) {
|
|
1829
|
-
console.log(`sip maximum register attempts reached`)
|
|
1830
|
-
this.#registerAttempt = 0
|
|
1831
|
-
await this.getUserAgent().transport.disconnect().catch((error) => console.error(error))
|
|
1832
|
-
this.#attemptReconnection()
|
|
1833
|
-
} else {
|
|
1834
|
-
this.#registerAttempt++
|
|
1835
|
-
console.log(`sip reconnect success and register retry ${this.#registerAttempt} of ${this.#registerAttempts}`)
|
|
1836
|
-
this.#attemptRegister()
|
|
1837
|
-
}
|
|
1838
|
-
// }
|
|
1839
|
-
}
|
|
1840
|
-
},
|
|
1841
|
-
requestOptions: {
|
|
1842
|
-
extraHeaders: this.#extraHeaders
|
|
1843
|
-
}
|
|
1844
|
-
})
|
|
1845
|
-
} catch (e) {
|
|
1846
|
-
console.log('sip reconnect then registerer register error', e)
|
|
1847
|
-
this.#registerAttempt = 0
|
|
1848
|
-
this.#registerAttempting = false
|
|
1849
|
-
}
|
|
1850
|
-
}, this.#registerInterval * 1000)
|
|
1851
|
-
}
|
|
1852
|
-
}
|
|
1853
|
-
|
|
1854
|
-
/**
|
|
1855
|
-
* 重连
|
|
1856
|
-
* @param { number } reconnectionAttempt 第几次重连
|
|
1857
|
-
* @private
|
|
1858
|
-
*/
|
|
1859
|
-
async #attemptReconnection (reconnectionAttempt = 1): Promise<void> {
|
|
1860
|
-
if (this.#reconnectionInterval) {
|
|
1861
|
-
this.#userAgentStatus.reconnectStatus = true
|
|
1862
|
-
if (this.#reconnectionAttempting) {
|
|
1863
|
-
return
|
|
1864
|
-
}
|
|
1865
|
-
if (reconnectionAttempt > this.#reconnectionAttempts) {
|
|
1866
|
-
console.log(`sip maximum reconnection attempts reached`)
|
|
1867
|
-
await this.dispose()
|
|
1868
|
-
return
|
|
1869
|
-
}
|
|
1870
|
-
console.log('sip reconnection attempt...')
|
|
1871
|
-
this.#reconnectionAttempting = true
|
|
1872
|
-
this.#reconnectionTimer = setTimeout(() => {
|
|
1873
|
-
this.#clearReconnectTimer()
|
|
1874
|
-
this.getUserAgent().reconnect()
|
|
1875
|
-
.then(async () => {
|
|
1876
|
-
console.log(`sip reconnection attempt ${reconnectionAttempt} of ${this.#reconnectionAttempts} - succeeded`)
|
|
1877
|
-
this.#reconnectionAttempting = false
|
|
1878
|
-
this.#attemptRegister()
|
|
1879
|
-
})
|
|
1880
|
-
.catch((error: Error) => {
|
|
1881
|
-
console.error(error)
|
|
1882
|
-
console.log(`sip reconnection attempt ${reconnectionAttempt} of ${this.#reconnectionAttempts} - failed`)
|
|
1883
|
-
this.#reconnectionAttempting = false
|
|
1884
|
-
this.#attemptReconnection(++reconnectionAttempt)
|
|
1885
|
-
})
|
|
1886
|
-
}, reconnectionAttempt === 1 ? 0 : this.#reconnectionInterval * 1000)
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
|
|
1890
|
-
/**
|
|
1891
|
-
* 实例代理事件混入
|
|
1892
|
-
*/
|
|
1893
|
-
#extendDelegate (delegate: UserAgentDelegate) {
|
|
1894
|
-
Object.keys(delegate).forEach(userAgentDelegateKey => {
|
|
1895
|
-
const key = userAgentDelegateKey as UserAgentDelegateKey
|
|
1896
|
-
if (typeof delegate?.[key] === 'function') {
|
|
1897
|
-
const tmp = delegate[key] as Function
|
|
1898
|
-
// @ts-ignore
|
|
1899
|
-
delegate[key] = async (invitation: Invitation) => {
|
|
1900
|
-
// @ts-ignore
|
|
1901
|
-
this.#userAgentOption.delegate![key]?.(invitation)
|
|
1902
|
-
await tmp(invitation)
|
|
1903
|
-
}
|
|
1904
|
-
}
|
|
1905
|
-
})
|
|
1906
|
-
return delegate
|
|
1907
|
-
}
|
|
1908
|
-
|
|
1909
|
-
/**
|
|
1910
|
-
* 代理事件
|
|
1911
|
-
* @param delegate
|
|
1912
|
-
* @private
|
|
1913
|
-
*/
|
|
1914
|
-
#bindUserAgent (delegate: UserAgentDelegate) {
|
|
1915
|
-
const userAgent = this.getUserAgent()
|
|
1916
|
-
// @ts-ignore
|
|
1917
|
-
userAgent.delegate = this.#extendDelegate(delegate)
|
|
1918
|
-
return userAgent
|
|
1919
|
-
}
|
|
1920
|
-
|
|
1921
|
-
/**
|
|
1922
|
-
* 连接
|
|
1923
|
-
* @param {{ refresh: (path: keyof UserAgentStatusKey, value: boolean) => void }} config
|
|
1924
|
-
* @private
|
|
1925
|
-
*/
|
|
1926
|
-
async #connectUserAgent (config: { refresh: (path: UserAgentStatusKey, value: boolean) => void }) {
|
|
1927
|
-
this.#initStatus(config)
|
|
1928
|
-
const userAgent = this.getUserAgent()
|
|
1929
|
-
await userAgent.start()
|
|
1930
|
-
if (!userAgent.isConnected()) { // 二次重连
|
|
1931
|
-
await userAgent.reconnect()
|
|
1932
|
-
}
|
|
1933
|
-
if (!userAgent.isConnected()) {
|
|
1934
|
-
throw new Error('链接失败,请稍后再试')
|
|
1935
|
-
}
|
|
1936
|
-
this.#userAgentStatus.connectStatus = true
|
|
1937
|
-
return userAgent
|
|
1938
|
-
}
|
|
1939
|
-
|
|
1940
|
-
/**
|
|
1941
|
-
* 注册
|
|
1942
|
-
* @private
|
|
1943
|
-
* @param options 配置
|
|
1944
|
-
* @param { import('sip.js/lib/api/registerer-options').RegistererOptions } options.registererOptions new Registerer Options
|
|
1945
|
-
* @param { import('sip.js/lib/api/registerer-register-options').RegistererRegisterOptions } options.registererRegisterOptions Registerer Instance register Options
|
|
1946
|
-
* @param { string[] } options.extraHeaders sip custom header
|
|
1947
|
-
*/
|
|
1948
|
-
#registerUserAgent (options?: RegisterOptions) {
|
|
1949
|
-
const {
|
|
1950
|
-
registererOptions,
|
|
1951
|
-
registererRegisterOptions,
|
|
1952
|
-
extraHeaders
|
|
1953
|
-
} = (options || {})
|
|
1954
|
-
return new Promise<UserAgent>(async (resolve, reject) => {
|
|
1955
|
-
const userAgent = this.getUserAgent()
|
|
1956
|
-
// @ts-ignore
|
|
1957
|
-
this.#registerer = new Registerer(userAgent, {
|
|
1958
|
-
extraHeaders: extraHeaders || this.#extraHeaders,
|
|
1959
|
-
...(registererOptions || {})
|
|
1960
|
-
})
|
|
1961
|
-
try {
|
|
1962
|
-
const option = (registererRegisterOptions || {})
|
|
1963
|
-
const requestDelegate = this.#extendFreeDelegate({
|
|
1964
|
-
onAccept: (response: IncomingResponse) => {
|
|
1965
|
-
this.#userAgentStatus.registerStatus = true
|
|
1966
|
-
resolve(userAgent)
|
|
1967
|
-
},
|
|
1968
|
-
onReject: (response: IncomingResponse) => {
|
|
1969
|
-
reject(new Error('sip register fail with code ' + response.message.statusCode))
|
|
1970
|
-
}
|
|
1971
|
-
}, option.requestDelegate as Record<string, Function>)
|
|
1972
|
-
delete option.requestDelegate
|
|
1973
|
-
await this.#registerer.register({
|
|
1974
|
-
// @ts-ignore
|
|
1975
|
-
requestDelegate,
|
|
1976
|
-
requestOptions: {
|
|
1977
|
-
extraHeaders: extraHeaders || this.#extraHeaders
|
|
1978
|
-
},
|
|
1979
|
-
...option
|
|
1980
|
-
})
|
|
1981
|
-
} catch (e) {
|
|
1982
|
-
reject(e)
|
|
1983
|
-
}
|
|
1984
|
-
})
|
|
1985
|
-
}
|
|
1986
|
-
}
|