@94ai/softphone 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarnrc.yml +19 -0
- package/README.md +941 -0
- package/bin/index.js +50 -0
- package/html-softphone-demo/CryptoJS.js +3 -0
- package/html-softphone-demo/README.md +10 -0
- package/html-softphone-demo/RootCA.pem +20 -0
- package/html-softphone-demo/antique_phone.mp3 +0 -0
- package/html-softphone-demo/cert.pem +21 -0
- package/html-softphone-demo/fp.umd.min.js +9 -0
- package/html-softphone-demo/gg.xccjh.top.key +27 -0
- package/html-softphone-demo/gg.xccjh.top.pem +61 -0
- package/html-softphone-demo/index-test.html +867 -0
- package/html-softphone-demo/index.html +845 -0
- package/html-softphone-demo/localhost.crt +21 -0
- package/html-softphone-demo/localhost.key +28 -0
- package/html-softphone-demo/pcm-player.js +131 -0
- package/html-softphone-demo/private.key +28 -0
- package/html-softphone-demo/softphone.umd.min.js +2 -0
- package/html-softphone-demo/tapd_35238004_base64_1695010473_859.gif +0 -0
- package/html-softphone-demo/test.html +190 -0
- package/html-softphone-demo.7z +0 -0
- package/lib/index.d.ts +531 -0
- package/lib/softphone.cjs.min.cjs +1 -0
- package/lib/softphone.esm-bundler.min.mjs +1 -0
- package/lib/softphone.umd.min.js +2 -0
- package/package.json +88 -0
- package/src/const/index.ts +120 -0
- package/src/index.ts +123 -0
- package/src/sdk/index.ts +1986 -0
- package/src/types/index.ts +343 -0
- package/src/utils/index.ts +301 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
import type { UserAgentDelegate } from 'sip.js/lib/api/user-agent-delegate'
|
|
2
|
+
import type { UserAgentOptions as SipUserAgentOptions } from 'sip.js/lib/api/user-agent-options'
|
|
3
|
+
import { URI } from 'sip.js'
|
|
4
|
+
import { InvitationRejectOptions } from 'sip.js/lib/api/invitation-reject-options'
|
|
5
|
+
import { SessionByeOptions } from 'sip.js/lib/api/session-bye-options'
|
|
6
|
+
import { RegistererOptions } from 'sip.js/lib/api/registerer-options'
|
|
7
|
+
import { RegistererRegisterOptions } from 'sip.js/lib/api/registerer-register-options'
|
|
8
|
+
import { InviterInviteOptions } from 'sip.js/lib/api/inviter-invite-options'
|
|
9
|
+
import { SessionInfoOptions } from 'sip.js/lib/api/session-info-options'
|
|
10
|
+
import { InvitationAcceptOptions } from 'sip.js/lib/api/invitation-accept-options'
|
|
11
|
+
|
|
12
|
+
export type InviterInviteOptionsExtend = InviterInviteOptions & {extraHeaders?: string[]}
|
|
13
|
+
export type SessionInfoOptionsExtend = SessionInfoOptions & {extraHeaders?: string[]}
|
|
14
|
+
export type InvitationAcceptOptionsExtend = InvitationAcceptOptions & {onAck?: Function, onAckTimeout?: Function }
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 挂断配置
|
|
18
|
+
*/
|
|
19
|
+
export type HandUpInviteOption = {
|
|
20
|
+
/** 当会话尚在建立中,配置挂断请求 */
|
|
21
|
+
rejectOptions?: InvitationRejectOptions,
|
|
22
|
+
/** 当会话已建立,配置挂断请求 */
|
|
23
|
+
byeOptions?: SessionByeOptions
|
|
24
|
+
/** sip自定义头 */
|
|
25
|
+
extraHeaders?: string[]
|
|
26
|
+
/** 是否开启响应返回检测 */
|
|
27
|
+
scoutResponse?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 注册配置
|
|
32
|
+
*/
|
|
33
|
+
export type RegisterOptions = {
|
|
34
|
+
/** sip自定义头 */
|
|
35
|
+
extraHeaders?: string[]
|
|
36
|
+
/** new Registerer Options */
|
|
37
|
+
registererOptions?: RegistererOptions
|
|
38
|
+
/** Registerer Instance register Options */
|
|
39
|
+
registererRegisterOptions?: RegistererRegisterOptions
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 签入配置
|
|
44
|
+
*/
|
|
45
|
+
export type PrepareUserAgentOptions = {
|
|
46
|
+
/** sip服务地址,可以用于切换签入服务器 */
|
|
47
|
+
uri?: URI,
|
|
48
|
+
/** 分机号,可以用于切换签入服务器 */
|
|
49
|
+
authorizationUsername?: string,
|
|
50
|
+
/** 分机密码,可以用于切换签入服务器 */
|
|
51
|
+
authorizationPassword?: string,
|
|
52
|
+
/** transport层协议配置,可以用于切换签入服务器 */
|
|
53
|
+
transportOptions?: TransportOptions,
|
|
54
|
+
/** 一般设置同authorizationUsername,相当于MicroSIP的显示名称,可以用于切换签入服务器 */
|
|
55
|
+
contactName?: string
|
|
56
|
+
/** sip自定义头 */
|
|
57
|
+
extraHeaders?: string[]
|
|
58
|
+
/** 当软电话状态变化时会实时刷新这个方法 */
|
|
59
|
+
refresh?: (path: UserAgentStatusKey, value: boolean) => void,
|
|
60
|
+
/** new Registerer Options */
|
|
61
|
+
registererOptions?: RegistererOptions
|
|
62
|
+
/** Registerer Instance register Options */
|
|
63
|
+
registererRegisterOptions?: RegistererRegisterOptions
|
|
64
|
+
agentId?: number
|
|
65
|
+
agentTag?: string
|
|
66
|
+
appKey?: string
|
|
67
|
+
appSecret?: string
|
|
68
|
+
openBaseUrl?: string
|
|
69
|
+
sg?: '1' | '0'
|
|
70
|
+
sgOpen?: '1' | '0'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 软电话代理实例状态
|
|
75
|
+
*/
|
|
76
|
+
export type UserAgentStatus = {
|
|
77
|
+
/** 软电话是否已签入 */
|
|
78
|
+
connectStatus: boolean,
|
|
79
|
+
/** 软电话是否已注册 */
|
|
80
|
+
registerStatus: boolean,
|
|
81
|
+
/** 软电话是否正拨出 */
|
|
82
|
+
invitatingStatus: boolean,
|
|
83
|
+
/** 软电话是否正来电 */
|
|
84
|
+
incomingStatus: boolean,
|
|
85
|
+
/** 软电话是否正接听 */
|
|
86
|
+
answerStatus: boolean
|
|
87
|
+
/**
|
|
88
|
+
* 软电话是否正在重连
|
|
89
|
+
* 网络断掉,服务器重启,宕机等引起服务不可达时软电话代理会尝试重新连接并签入
|
|
90
|
+
* 这个时候用户拨出等动作可以通过此状态做拦截,通知用户软电话服务器可能正重启或断网等导致服务不可用
|
|
91
|
+
* */
|
|
92
|
+
reconnectStatus: boolean
|
|
93
|
+
}
|
|
94
|
+
export type UserAgentDelegateKey = keyof UserAgentDelegate
|
|
95
|
+
export type UserAgentStatusKey = 'connectStatus' | 'registerStatus' | 'invitatingStatus' | 'incomingStatus' | 'answerStatus'
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* transport层配置
|
|
99
|
+
*/
|
|
100
|
+
export interface TransportOptions {
|
|
101
|
+
/** websocket协商地址, server和wsServers 必须二选一*/
|
|
102
|
+
server?: string
|
|
103
|
+
/** 多个地址开启负载均衡模式 */
|
|
104
|
+
wsServers?: string | string[] | {
|
|
105
|
+
/** websocket协商地址 */
|
|
106
|
+
ws_uri: string,
|
|
107
|
+
/** 权重 */
|
|
108
|
+
weight: number
|
|
109
|
+
}[]
|
|
110
|
+
/**
|
|
111
|
+
* websocke初始化连接等待超时时间
|
|
112
|
+
* @default 5
|
|
113
|
+
*/
|
|
114
|
+
connectionTimeout?: number
|
|
115
|
+
/**
|
|
116
|
+
* transport层客户端保活最大重连尝试次数
|
|
117
|
+
* @default 3
|
|
118
|
+
*/
|
|
119
|
+
maxReconnectionAttempts?: number
|
|
120
|
+
/**
|
|
121
|
+
* transport层客户端保活重连动作执行间隔时间,单位秒,同UserAgentOptionsSDK.reconnectionDelay
|
|
122
|
+
* @default 4
|
|
123
|
+
*/
|
|
124
|
+
reconnectionTimeout?: number
|
|
125
|
+
/**
|
|
126
|
+
* transport层The time (Number) in seconds to wait in between CLRF keepAlive sequences are sent.
|
|
127
|
+
* @default 0
|
|
128
|
+
*/
|
|
129
|
+
keepAliveInterval?: number
|
|
130
|
+
/**
|
|
131
|
+
* transport层The time (Number) in seconds to debounce sending CLRF keepAlive sequences by
|
|
132
|
+
* @default 10
|
|
133
|
+
*/
|
|
134
|
+
keepAliveDebounce?: number
|
|
135
|
+
/**
|
|
136
|
+
* transport层If true, messages sent and received by the transport are logged.
|
|
137
|
+
* @default false
|
|
138
|
+
*/
|
|
139
|
+
traceSip?: boolean
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* sip层配置
|
|
144
|
+
*/
|
|
145
|
+
export interface UserAgentOptionsSDK {
|
|
146
|
+
/**
|
|
147
|
+
* 通过openApi获取坐席账号分机密码
|
|
148
|
+
* @default ''
|
|
149
|
+
*/
|
|
150
|
+
authorizationPassword: string,
|
|
151
|
+
/**
|
|
152
|
+
* 通过openApi获取坐席账号分机用户名
|
|
153
|
+
* @default ''
|
|
154
|
+
*/
|
|
155
|
+
authorizationUsername: string,
|
|
156
|
+
/**
|
|
157
|
+
* 指纹,唯一标志,用来排查线路故障,默认随机指纹,可选
|
|
158
|
+
* @default createRandomToken(12) + ".invalid"
|
|
159
|
+
*/
|
|
160
|
+
viaHost?: string,
|
|
161
|
+
/**
|
|
162
|
+
* sip服务地址,必填,需要服务可达的地址
|
|
163
|
+
* @default new URI("sip", "anonymous." + createRandomToken(6), "anonymous.invalid") })
|
|
164
|
+
*/
|
|
165
|
+
uri: URI,
|
|
166
|
+
/**
|
|
167
|
+
* sip日志查看等级,一般情况下生产开error,开发用debugger
|
|
168
|
+
* @default 'log'
|
|
169
|
+
*/
|
|
170
|
+
logLevel?: 'debug' | 'log' | 'warn' | 'error',
|
|
171
|
+
/**
|
|
172
|
+
* 一般设置同authorizationUsername,相当于MicroSIP的显示名称
|
|
173
|
+
* @default createRandomToken(8)
|
|
174
|
+
*/
|
|
175
|
+
contactName?: string
|
|
176
|
+
/**
|
|
177
|
+
* 签入来电后多长时间不执行接听会话自动结束会话,单位秒
|
|
178
|
+
* @default 60
|
|
179
|
+
*/
|
|
180
|
+
noAnswerTimeout?: number,
|
|
181
|
+
/**
|
|
182
|
+
* sip层 - 重连尝试间隔,为了兼容sipjs,同reconnectionInterval
|
|
183
|
+
* @deprecated
|
|
184
|
+
* @default 100
|
|
185
|
+
*/
|
|
186
|
+
reconnectionDelay?: number,
|
|
187
|
+
/**
|
|
188
|
+
* transport层协议配置
|
|
189
|
+
*/
|
|
190
|
+
transportOptions: TransportOptions,
|
|
191
|
+
/**
|
|
192
|
+
* sip层 - 重连尝试间隔,单位秒
|
|
193
|
+
* @default 100
|
|
194
|
+
*/
|
|
195
|
+
reconnectionInterval?: number,
|
|
196
|
+
/**
|
|
197
|
+
* sip层 - 重连失败最大尝试次数
|
|
198
|
+
* @default
|
|
199
|
+
*/
|
|
200
|
+
reconnectionAttempts?: number,
|
|
201
|
+
/**
|
|
202
|
+
* sip层 - 重连成功后尝试重新注册检间隔,单位秒
|
|
203
|
+
* @default 3
|
|
204
|
+
*/
|
|
205
|
+
registerInterval?: number,
|
|
206
|
+
/**
|
|
207
|
+
* sip层 - 重连成功最大尝试注册次数
|
|
208
|
+
* @default 3
|
|
209
|
+
*/
|
|
210
|
+
registerAttempts?: number
|
|
211
|
+
/**
|
|
212
|
+
* sip层 - ping动作间隔,单位秒
|
|
213
|
+
* @default 8
|
|
214
|
+
*/
|
|
215
|
+
optionsPingInterval?: number
|
|
216
|
+
/**
|
|
217
|
+
* sip层 - ping最大失败尝试次数后开始重连,防止网络抖动引起非必要重连
|
|
218
|
+
* @default 3
|
|
219
|
+
*/
|
|
220
|
+
optionsPingAttempts?: number
|
|
221
|
+
/**
|
|
222
|
+
* sip层 - 自定义通讯header
|
|
223
|
+
*/
|
|
224
|
+
sipHeaders?: Array<string>
|
|
225
|
+
appKey?: string
|
|
226
|
+
appSecret?: string
|
|
227
|
+
agentTag?: string
|
|
228
|
+
agentId?: number
|
|
229
|
+
openBaseUrl?: string
|
|
230
|
+
sg?: '1' | '0'
|
|
231
|
+
sgOpen?: '1' | '0'
|
|
232
|
+
token: string,
|
|
233
|
+
tokenTimestamp: string | number,
|
|
234
|
+
tokenExpirationTime: number,
|
|
235
|
+
sign: string,
|
|
236
|
+
timestamp: string | number,
|
|
237
|
+
signExpirationTime: number,
|
|
238
|
+
signCheck?: Function
|
|
239
|
+
tokenCheck?: Function
|
|
240
|
+
signOverdued?: Function
|
|
241
|
+
tokenOverdued?: Function
|
|
242
|
+
openXhrIntercept?: Function
|
|
243
|
+
gatewayXhrIntercept?: Function
|
|
244
|
+
refreshChatErrorCallback?: Function
|
|
245
|
+
refreshSpeekVolumn?: Function
|
|
246
|
+
refreshRequirementCheck?: Function
|
|
247
|
+
refreshChat?: Function
|
|
248
|
+
enableChatInfoPush?: boolean
|
|
249
|
+
enableVolumnTrack?: boolean
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export type UserAgentOptions = SipUserAgentOptions & UserAgentOptionsSDK
|
|
253
|
+
|
|
254
|
+
export type ajaxOption = {
|
|
255
|
+
type: 'GET' | 'POST',
|
|
256
|
+
url: string,
|
|
257
|
+
data: Record<string, any>,
|
|
258
|
+
contentType: string,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export enum CallType {
|
|
262
|
+
'坐席-人工外呼' = 1001,
|
|
263
|
+
'坐席-AI 外呼-不转人工' = 1002,
|
|
264
|
+
'坐席-AI 外呼-接通转人工' = 1003,
|
|
265
|
+
'坐席-AI 外呼-智能转人工' = 1004,
|
|
266
|
+
'批量-预测外呼' = 2001,
|
|
267
|
+
'批量-AI 外呼-不转人工' = 2002,
|
|
268
|
+
'批量-AI 外呼-接通转人工' = 2003,
|
|
269
|
+
'批量-AI 外呼-智能转人工' = 2004,
|
|
270
|
+
'批量-语音通知' = 2005,
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface PushData {
|
|
274
|
+
/**
|
|
275
|
+
* 是否停止轮询
|
|
276
|
+
*/
|
|
277
|
+
stop: boolean
|
|
278
|
+
/**
|
|
279
|
+
* 任务 ID
|
|
280
|
+
*/
|
|
281
|
+
taskId: string
|
|
282
|
+
/**
|
|
283
|
+
* 分机号
|
|
284
|
+
*/
|
|
285
|
+
extensionNumber: string
|
|
286
|
+
/**
|
|
287
|
+
* 外呼 ID
|
|
288
|
+
*/
|
|
289
|
+
callid: string
|
|
290
|
+
/**
|
|
291
|
+
* 外呼 ID
|
|
292
|
+
*/
|
|
293
|
+
callId: string
|
|
294
|
+
/**
|
|
295
|
+
* 外呼类型
|
|
296
|
+
*/
|
|
297
|
+
callType: CallType
|
|
298
|
+
/**
|
|
299
|
+
* 意向标签
|
|
300
|
+
*/
|
|
301
|
+
intentTag: string
|
|
302
|
+
/**
|
|
303
|
+
* 外呼号码
|
|
304
|
+
*/
|
|
305
|
+
number: string
|
|
306
|
+
/**
|
|
307
|
+
* 号码 ID
|
|
308
|
+
*/
|
|
309
|
+
numberMD5: string
|
|
310
|
+
/**
|
|
311
|
+
* 分配坐席 ID
|
|
312
|
+
*/
|
|
313
|
+
agentId: number
|
|
314
|
+
/**
|
|
315
|
+
* 坐席标签
|
|
316
|
+
*/
|
|
317
|
+
agentTag: string
|
|
318
|
+
/**
|
|
319
|
+
* 用户标签
|
|
320
|
+
*/
|
|
321
|
+
tag: string
|
|
322
|
+
/**
|
|
323
|
+
* AI 话术 ID
|
|
324
|
+
*/
|
|
325
|
+
templateId: string
|
|
326
|
+
chats: {
|
|
327
|
+
/**
|
|
328
|
+
* 说话内容
|
|
329
|
+
*/
|
|
330
|
+
"content": string,
|
|
331
|
+
/**
|
|
332
|
+
* 说话时间
|
|
333
|
+
*/
|
|
334
|
+
"createTime": string,
|
|
335
|
+
/**
|
|
336
|
+
* 说话号码
|
|
337
|
+
*/
|
|
338
|
+
"fromNumber": string
|
|
339
|
+
}[]
|
|
340
|
+
[index:string]:any
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Inviter,
|
|
3
|
+
SessionState,
|
|
4
|
+
UserAgent
|
|
5
|
+
} from 'sip.js'
|
|
6
|
+
import { ajaxOption } from '../types'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 获取音频标签
|
|
10
|
+
* @param id Element id
|
|
11
|
+
*/
|
|
12
|
+
export function getMedia (id: string): HTMLAudioElement | undefined {
|
|
13
|
+
return document.getElementById(id) as HTMLAudioElement
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 释放 mediaStream
|
|
18
|
+
* @param stream
|
|
19
|
+
*/
|
|
20
|
+
export function stopStreamTracks (stream: any) {
|
|
21
|
+
if (!stream || !stream.getTracks) {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const tracks = stream.getTracks()
|
|
26
|
+
tracks.forEach((it: any) => {
|
|
27
|
+
try {
|
|
28
|
+
it.stop()
|
|
29
|
+
} catch (errMsg) {
|
|
30
|
+
// debugger;
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
} catch (errMsg) {
|
|
34
|
+
// debugger;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 获取设备权限
|
|
40
|
+
* @param constraints
|
|
41
|
+
*/
|
|
42
|
+
export function getDevicePermission (constraints: any) {
|
|
43
|
+
return navigator.mediaDevices
|
|
44
|
+
.getUserMedia(constraints)
|
|
45
|
+
.then(stream => {
|
|
46
|
+
if (stream) {
|
|
47
|
+
stopStreamTracks(stream)
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
return Promise.reject(new Error('EmptyStreamError'))
|
|
51
|
+
})
|
|
52
|
+
.catch(errMsg => {
|
|
53
|
+
if (errMsg && errMsg.name === 'NotAllowedError') {
|
|
54
|
+
return false
|
|
55
|
+
}
|
|
56
|
+
return Promise.reject(errMsg)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 请求设备权限
|
|
62
|
+
*/
|
|
63
|
+
export function requestMicroPhonePermission () {
|
|
64
|
+
try {
|
|
65
|
+
if (navigator.userAgent.indexOf('Firefox') === -1) {
|
|
66
|
+
return getDevicePermission({ video: false, audio: true }).catch(() => true)
|
|
67
|
+
}
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.log(e)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 来电振铃
|
|
75
|
+
* @param id Element id
|
|
76
|
+
*/
|
|
77
|
+
export function playMedia (id: string): void {
|
|
78
|
+
const localAudioDom = getMedia(id)
|
|
79
|
+
if (localAudioDom) {
|
|
80
|
+
localAudioDom.currentTime = 0
|
|
81
|
+
localAudioDom.play()
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 关闭振铃
|
|
87
|
+
* @param id Element id
|
|
88
|
+
*/
|
|
89
|
+
export function pauseMedia (id: string): void {
|
|
90
|
+
const localAudioDom = getMedia(id)
|
|
91
|
+
if (localAudioDom) {
|
|
92
|
+
localAudioDom.currentTime = 0
|
|
93
|
+
localAudioDom.pause()
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 挂断通话
|
|
99
|
+
* @param id Element id
|
|
100
|
+
*/
|
|
101
|
+
export function cleanupMedia (id: string): void {
|
|
102
|
+
const mediaElement = getMedia(id)
|
|
103
|
+
if (mediaElement) {
|
|
104
|
+
mediaElement.srcObject = null
|
|
105
|
+
mediaElement.pause()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 发送
|
|
111
|
+
*/
|
|
112
|
+
export const onSendCall = (userAgent: InstanceType<typeof UserAgent>, target: any) => {
|
|
113
|
+
const inviter = new Inviter(userAgent, target)
|
|
114
|
+
inviter.stateChange.addListener((state) => {
|
|
115
|
+
switch (state) {
|
|
116
|
+
case SessionState.Initial:
|
|
117
|
+
break
|
|
118
|
+
case SessionState.Establishing:
|
|
119
|
+
break
|
|
120
|
+
case SessionState.Established:
|
|
121
|
+
// setupMedia(inviter);
|
|
122
|
+
break
|
|
123
|
+
case SessionState.Terminating:
|
|
124
|
+
// fall through
|
|
125
|
+
case SessionState.Terminated:
|
|
126
|
+
cleanupMedia('remoteAudio')
|
|
127
|
+
break
|
|
128
|
+
default:
|
|
129
|
+
throw new Error('Unknown session state.')
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
inviter.invite()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 获取时间字符串
|
|
137
|
+
* @param { Date }} time 需要转换的时间对象
|
|
138
|
+
* @return xx:xx:xx
|
|
139
|
+
*/
|
|
140
|
+
export const getTimes = (time: Date) => {
|
|
141
|
+
return time.getHours().toString().padStart(2, '0') + ':' + time.getMinutes().toString().padStart(2, '0') + ':' + time.getSeconds().toString().padStart(2, '0')
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* 获取0点时间
|
|
145
|
+
* @return Date
|
|
146
|
+
*/
|
|
147
|
+
export const getZeorTime = () => {
|
|
148
|
+
return new Date(new Date(new Date().toLocaleDateString()).getTime())
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 加1s时间
|
|
152
|
+
* @param { Date } date 需要操作的时间对象
|
|
153
|
+
* @return Date
|
|
154
|
+
*/
|
|
155
|
+
export const accumulateSec = (date: Date) => {
|
|
156
|
+
return new Date(date.setSeconds(date.getSeconds() + 1))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 计时器
|
|
161
|
+
* @param { Function } refresh 回调
|
|
162
|
+
* @return Function 销毁计时器
|
|
163
|
+
*/
|
|
164
|
+
export function accumulationTimer (refresh: Function) {
|
|
165
|
+
const date = getZeorTime()
|
|
166
|
+
refresh(getTimes(date))
|
|
167
|
+
const calc = () => {
|
|
168
|
+
const addtime = accumulateSec(date)
|
|
169
|
+
const times = getTimes(addtime)
|
|
170
|
+
refresh(times)
|
|
171
|
+
}
|
|
172
|
+
let timer = setInterval(calc, 1000);
|
|
173
|
+
return () => {
|
|
174
|
+
clearInterval(timer)
|
|
175
|
+
timer = null
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 转换queryParams
|
|
181
|
+
* @param url
|
|
182
|
+
*/
|
|
183
|
+
export function getQueryObject (location = window.location) {
|
|
184
|
+
const hashUrl = location.hash
|
|
185
|
+
const searchUrl = location.search
|
|
186
|
+
const obj:Record<string, string> = {}
|
|
187
|
+
const reg = /([^?&=]+)=([^?&=]*)/g
|
|
188
|
+
const hash = hashUrl.substring(hashUrl.indexOf('?') + 1)
|
|
189
|
+
const search = searchUrl.substring(1)
|
|
190
|
+
// @ts-ignore
|
|
191
|
+
hash.replace(reg, (rs, $1, $2) => {
|
|
192
|
+
const name = decodeURIComponent($1)
|
|
193
|
+
let val = decodeURIComponent($2)
|
|
194
|
+
val = String(val)
|
|
195
|
+
obj[name] = val
|
|
196
|
+
return rs
|
|
197
|
+
})
|
|
198
|
+
// @ts-ignore
|
|
199
|
+
search.replace(reg, (rs, $1, $2) => {
|
|
200
|
+
const name = decodeURIComponent($1)
|
|
201
|
+
let val = decodeURIComponent($2)
|
|
202
|
+
val = String(val)
|
|
203
|
+
obj[name] = val
|
|
204
|
+
return rs
|
|
205
|
+
})
|
|
206
|
+
return obj
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function getTop () {
|
|
210
|
+
if (getQueryObject(location).cross === '1') {
|
|
211
|
+
return window
|
|
212
|
+
}
|
|
213
|
+
return top
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function params(json: Record<string, string>){
|
|
217
|
+
let paramArr = []
|
|
218
|
+
for (let p in json) {
|
|
219
|
+
paramArr.push(p + '=' + json[p])
|
|
220
|
+
}
|
|
221
|
+
return paramArr.join('&')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function isDomainOrIP (str: string) {
|
|
225
|
+
if (!str) {
|
|
226
|
+
return false
|
|
227
|
+
}
|
|
228
|
+
const ipPattern = /^(\d{1,3}\.){3}\d{1,3}$/
|
|
229
|
+
const domainPattern = /^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$/
|
|
230
|
+
if (ipPattern.test(str)) {
|
|
231
|
+
return true
|
|
232
|
+
}
|
|
233
|
+
if (str.slice(0, 2) === '//') {
|
|
234
|
+
return domainPattern.test(str.slice(2, str.length))
|
|
235
|
+
}
|
|
236
|
+
if (str.slice(0, 7) === 'http://') {
|
|
237
|
+
return domainPattern.test(str.slice(7, str.length))
|
|
238
|
+
}
|
|
239
|
+
if (str.slice(0, 8) === 'https://') {
|
|
240
|
+
return domainPattern.test(str.slice(8, str.length))
|
|
241
|
+
}
|
|
242
|
+
return domainPattern.test(str)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function getAjax(resolve: Function, reject: Function, param: Partial<ajaxOption>) {
|
|
246
|
+
// @ts-ignore
|
|
247
|
+
const xhr = window.XMLHttpRequest ? new window.XMLHttpRequest() : window.ActiveXObject ? new window.ActiveXObject('Microsoft.XMLHTTP') : undefined // 兼容IE6 IE5
|
|
248
|
+
if (!xhr) {
|
|
249
|
+
reject(new Error('Your Browser does not support ajax'))
|
|
250
|
+
}
|
|
251
|
+
xhr.onreadystatechange = function () {
|
|
252
|
+
// 0:请求初始化,对象刚刚创建
|
|
253
|
+
// 1:服务器已连接
|
|
254
|
+
// 2:已发送,send发放已调用
|
|
255
|
+
// 3:已接收,此时只接收了响应(response)头部分
|
|
256
|
+
// 4:已接收,此时接收响应(response)体信息
|
|
257
|
+
if (xhr.readyState == 4) { // 每当 readyState 状态值发生改变时会,就会触发 onreadystatechange 事件,对应着每个状态值就会被触发五次。当状态值为 4 时表示网络请求响应完毕,就可以获取返回的值。
|
|
258
|
+
const res = xhr.response || xhr.responseText
|
|
259
|
+
// 1XX:信息类,表示收到web浏览器请求,正在进一步处理中
|
|
260
|
+
// 2XX:成功,表示用户请求被正确接收
|
|
261
|
+
// 3XX:重定向,表示请求没成功,需要客户采取进一步的动作(304表示请求的资源未修改,可以直接使用浏览器的缓存版本)
|
|
262
|
+
// 4XX:客户端错误,表示客户端提交的请求有错误,如:404 Not Found,意味着请求中所引用的文档不存在
|
|
263
|
+
// 5XX:服务器错误,表示服务器不能完成对请求的处理
|
|
264
|
+
if (xhr.status == 200) {
|
|
265
|
+
//接收返回的数据类型
|
|
266
|
+
const type = xhr.getResponseHeader('Content-Type');
|
|
267
|
+
if (type.indexOf('json') != -1){ //json格式
|
|
268
|
+
resolve(JSON.parse(xhr.responseText))
|
|
269
|
+
} else if(type.indexOf('xml') != -1){ //xml格式
|
|
270
|
+
resolve(xhr.responseXML);
|
|
271
|
+
} else{
|
|
272
|
+
resolve(xhr.responseText); //普通格式
|
|
273
|
+
}
|
|
274
|
+
} else {
|
|
275
|
+
reject(res)
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const {
|
|
280
|
+
url,
|
|
281
|
+
data,
|
|
282
|
+
type,
|
|
283
|
+
contentType,
|
|
284
|
+
} = param
|
|
285
|
+
const ajaxType = type || 'POST' //判断data是否为空
|
|
286
|
+
let ajaxUrl = url
|
|
287
|
+
let ajaxData = null
|
|
288
|
+
if (data) {
|
|
289
|
+
if(ajaxType === 'GET'){
|
|
290
|
+
ajaxUrl = ajaxUrl + '?'+ params(data);
|
|
291
|
+
} else if (ajaxType === 'POST') {
|
|
292
|
+
ajaxData = JSON.stringify(data)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
xhr.open(ajaxType, ajaxUrl, true)
|
|
296
|
+
return {
|
|
297
|
+
xhr,
|
|
298
|
+
ajaxData,
|
|
299
|
+
contentType
|
|
300
|
+
}
|
|
301
|
+
}
|