@subiz/wsclient 4.0.20 → 4.0.22
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/common.js +39 -1
- package/demo.html +129 -0
- package/demo.js +207 -0
- package/package.json +6 -4
- package/vite.config.js +16 -0
- package/webphone.js +80 -13
- package/webrtc.js +28 -8
package/common.js
CHANGED
|
@@ -1,3 +1,41 @@
|
|
|
1
|
+
function logError(accid, userid, message, line) {
|
|
2
|
+
// 24-01-11 04:07:05 app ERROR api-8948967b6-qtpfx subiz /app/vendor/github.com/subiz/log/error.go:271 {"id":141534190473829746,"class":500,"code":"locked_account,internal","number":"8f2b97e9","fields":{"ip":"\"171.224.84.93\"","no_report":"true","user_agent":"\"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36\""}}
|
|
3
|
+
let ts = displayUTCLogTime(new Date())
|
|
4
|
+
let msg = JSON.stringify({
|
|
5
|
+
id: Date.now(),
|
|
6
|
+
class: 400,
|
|
7
|
+
code: 'frontend_error',
|
|
8
|
+
number: line
|
|
9
|
+
.split('')
|
|
10
|
+
.map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))
|
|
11
|
+
.join(''),
|
|
12
|
+
fields: {
|
|
13
|
+
message: JSON.stringify(message),
|
|
14
|
+
user_id: userid,
|
|
15
|
+
url: JSON.stringify(location.href),
|
|
16
|
+
},
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
xhrsend('POST', 'https://track.subiz.net/collect', `${ts} app ERROR dashboard ${accid} ${line} ${msg}`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function displayUTCLogTime(date) {
|
|
23
|
+
let yyyy = date.getUTCFullYear().toString()
|
|
24
|
+
let yy = yyyy.substr(2, 2)
|
|
25
|
+
let mm = date.getUTCMonth() + 1
|
|
26
|
+
let dd = date.getUTCDate()
|
|
27
|
+
let hh = date.getUTCHours()
|
|
28
|
+
let MM = date.getUTCMinutes()
|
|
29
|
+
let ss = date.getUTCSeconds()
|
|
30
|
+
|
|
31
|
+
return `${yy}-${pad(mm)}-${pad(dd)} ${pad(hh)}:${pad(MM)}:${pad(ss)}`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function pad(v) {
|
|
35
|
+
if (v < 10) return `0${v}`
|
|
36
|
+
return `${v}`
|
|
37
|
+
}
|
|
38
|
+
|
|
1
39
|
function dofetch(method, url, body, cb) {
|
|
2
40
|
let headers = {}
|
|
3
41
|
if (body) headers['content-type'] = 'text/plain'
|
|
@@ -78,4 +116,4 @@ function randomString(len, base) {
|
|
|
78
116
|
return str
|
|
79
117
|
}
|
|
80
118
|
|
|
81
|
-
module.exports = {xhrsend, parseJSON, Pubsub, randomString}
|
|
119
|
+
module.exports = {xhrsend, parseJSON, Pubsub, randomString, logError}
|
package/demo.html
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link
|
|
6
|
+
rel="stylesheet"
|
|
7
|
+
href="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/css/bootstrap.min.css"
|
|
8
|
+
/>
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
10
|
+
<title>Subiz WebPhone Demo</title>
|
|
11
|
+
<style>
|
|
12
|
+
#info {
|
|
13
|
+
background: whitesmoke;
|
|
14
|
+
border-radius: 5px;
|
|
15
|
+
}
|
|
16
|
+
label {
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
margin-right: 10px;
|
|
19
|
+
}
|
|
20
|
+
.info_line {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: flex-start;
|
|
23
|
+
}
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body class="container pt-3">
|
|
27
|
+
<img
|
|
28
|
+
width="80"
|
|
29
|
+
src="https://storage.googleapis.com/sbz-email/images/d009afe944c1948998544e103ab0fef2_logo.png"
|
|
30
|
+
/>
|
|
31
|
+
<h1 class="mt-4">Subiz call demo</h1>
|
|
32
|
+
<button
|
|
33
|
+
class="btn btn-primary"
|
|
34
|
+
style="width: 120px"
|
|
35
|
+
id="btnHangup"
|
|
36
|
+
onClick="inputToken()"
|
|
37
|
+
>
|
|
38
|
+
Nhập token
|
|
39
|
+
</button>
|
|
40
|
+
<div id="tokenStatus" class="mt-2 text-danger">Chưa nhập token</div>
|
|
41
|
+
|
|
42
|
+
<div class="mt-3" style="width: 250px">
|
|
43
|
+
<div>
|
|
44
|
+
<label>Đầu số</label>
|
|
45
|
+
<input
|
|
46
|
+
class="form-control"
|
|
47
|
+
id="fromNumber"
|
|
48
|
+
type="text"
|
|
49
|
+
value="842473021368"
|
|
50
|
+
/>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="mt-3">
|
|
53
|
+
<label>Số khách</label>
|
|
54
|
+
<input
|
|
55
|
+
class="form-control"
|
|
56
|
+
placeholder="036..."
|
|
57
|
+
id="toNumber"
|
|
58
|
+
type="text"
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="mt-3">
|
|
62
|
+
<button
|
|
63
|
+
class="btn btn-success mr-3"
|
|
64
|
+
style="width: 150px"
|
|
65
|
+
id="btnCall"
|
|
66
|
+
disabled
|
|
67
|
+
onClick="makeCall()"
|
|
68
|
+
>
|
|
69
|
+
Gọi
|
|
70
|
+
</button>
|
|
71
|
+
<button
|
|
72
|
+
class="btn btn-outline-danger"
|
|
73
|
+
style="width: 79px"
|
|
74
|
+
id="btnHangup"
|
|
75
|
+
onClick="hangupCall()"
|
|
76
|
+
>
|
|
77
|
+
Ngắt
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<h3 class="mt-4">Thông tin cuộc gọi</h3>
|
|
82
|
+
<div class="p-2" id="info">
|
|
83
|
+
<div class="info_line">
|
|
84
|
+
<label>Call ID:</label>
|
|
85
|
+
<div id="infoCallId"></div>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="info_line">
|
|
88
|
+
<label>Conversation ID:</label>
|
|
89
|
+
<div id="infoConversationId"></div>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="info_line">
|
|
92
|
+
<label>Ghi âm:</label>
|
|
93
|
+
<a href="#" id="infoRecordedUrl" target="_blank"></a>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="info_line">
|
|
96
|
+
<label>Gọi từ số:</label>
|
|
97
|
+
<div id="infoFromNumber"></div>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="info_line">
|
|
100
|
+
<label>Gọi đến số:</label>
|
|
101
|
+
<div id="infoToNumber"></div>
|
|
102
|
+
</div>
|
|
103
|
+
<div class="info_line">
|
|
104
|
+
<label>Trạng thái:</label>
|
|
105
|
+
<div id="infoStatus"></div>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="info_line">
|
|
108
|
+
<label>Gọi lúc:</label>
|
|
109
|
+
<div id="infoCreated"></div>
|
|
110
|
+
</div>
|
|
111
|
+
<div class="info_line">
|
|
112
|
+
<label>Thời lượng (giây):</label>
|
|
113
|
+
<div id="infoDuration"></div>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="info_line">
|
|
116
|
+
<label>Mã ngắt máy:</label>
|
|
117
|
+
<div id="infoHangupCode"></div>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="mt-4">
|
|
121
|
+
<h3>Sự kiện realtime</h3>
|
|
122
|
+
<pre
|
|
123
|
+
style="padding: 10px 15px; border-radius: 5px; background: whitesmoke"
|
|
124
|
+
id="infoLog"
|
|
125
|
+
></pre>
|
|
126
|
+
</div>
|
|
127
|
+
<script type="module" src="./demo.js"></script>
|
|
128
|
+
</body>
|
|
129
|
+
</html>
|
package/demo.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
const SubizWebPhone = require('./webphone.js')
|
|
2
|
+
|
|
3
|
+
let webphone
|
|
4
|
+
var mycallid = 'x'
|
|
5
|
+
let accid
|
|
6
|
+
let eventLogs = {}
|
|
7
|
+
|
|
8
|
+
// setTokenStatus renders whether token is valid or invalid
|
|
9
|
+
function setTokenStatus(me) {
|
|
10
|
+
let $tokenStatus = document.getElementById('tokenStatus')
|
|
11
|
+
if (me == 'loading') {
|
|
12
|
+
$tokenStatus.innerHTML = 'Đang kiểm tra'
|
|
13
|
+
$tokenStatus.classList.add('text-muted')
|
|
14
|
+
$tokenStatus.classList.remove('text-danger')
|
|
15
|
+
$tokenStatus.classList.remove('text-success')
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!me || !me.account_id) {
|
|
20
|
+
$tokenStatus.innerHTML = 'Token rỗng hoặc không hợp lệ. Vui lòng nhập lại'
|
|
21
|
+
$tokenStatus.classList.remove('text-danger')
|
|
22
|
+
$tokenStatus.classList.remove('text-success')
|
|
23
|
+
$tokenStatus.classList.add('text-muted')
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
$tokenStatus.innerHTML = 'Token hợp lệ. Xin chào ' + me.fullname
|
|
28
|
+
$tokenStatus.classList.add('text-success')
|
|
29
|
+
$tokenStatus.classList.remove('text-danger')
|
|
30
|
+
$tokenStatus.classList.remove('text-muted')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function init() {
|
|
34
|
+
setTokenStatus('loading')
|
|
35
|
+
fetch(`https://api.subiz.com.vn/4.0/me?x-access-token=${access_token}`)
|
|
36
|
+
.then((response) => {
|
|
37
|
+
return response.json()
|
|
38
|
+
})
|
|
39
|
+
.then((me) => {
|
|
40
|
+
setTokenStatus(me)
|
|
41
|
+
if (!me || !me.account_id) return null
|
|
42
|
+
return me
|
|
43
|
+
})
|
|
44
|
+
.then((me) => {
|
|
45
|
+
if (!me) return
|
|
46
|
+
// save token
|
|
47
|
+
try {
|
|
48
|
+
webphone = new SubizWebPhone(access_token)
|
|
49
|
+
accid = access_token.split('_')[0]
|
|
50
|
+
} catch (e) {
|
|
51
|
+
alert('ERROR2: ' + e)
|
|
52
|
+
// setTimeout(main)
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
document.getElementById('btnCall').disabled = false
|
|
57
|
+
|
|
58
|
+
webphone.onEvent(function (ev) {
|
|
59
|
+
let call = ev.data.call_info
|
|
60
|
+
let callid = call.call_id
|
|
61
|
+
eventLogs[callid] = eventLogs[callid] || []
|
|
62
|
+
if (callid != mycallid) return
|
|
63
|
+
eventLogs[callid].push(ev)
|
|
64
|
+
|
|
65
|
+
// render log
|
|
66
|
+
let logM = eventLogs[callid]
|
|
67
|
+
if (logM) {
|
|
68
|
+
let log = logM
|
|
69
|
+
.map((ev) => {
|
|
70
|
+
return `${new Date(ev.created).toLocaleString()} | ${ev.type} | ${JSON.stringify(ev.data.call_info)}`
|
|
71
|
+
})
|
|
72
|
+
.join('\n')
|
|
73
|
+
document.getElementById('infoLog').innerHTML = log
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (call.status == 'dialing') {
|
|
77
|
+
document.getElementById('infoRecordedUrl').innerHTML = ''
|
|
78
|
+
document.getElementById('infoRecordedUrl').href = undefined
|
|
79
|
+
document.getElementById('info').style['background-color'] = '#fffed3'
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (call.status == 'active') {
|
|
83
|
+
document.getElementById('info').style['background-color'] = '#8cff6b'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (call.status == 'ended' || !call.status) {
|
|
87
|
+
document.getElementById('info').style['background-color'] = '#ffc6c6'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (call.status == 'ended' && ev.id) {
|
|
91
|
+
// from server, not fake
|
|
92
|
+
document.getElementById('infoRecordedUrl').innerHTML = 'Đang tải...'
|
|
93
|
+
document.getElementById('infoRecordedUrl').href = undefined
|
|
94
|
+
|
|
95
|
+
fetchRecordedUrl(callid).then((url) => {
|
|
96
|
+
if (url == 'unavailable') {
|
|
97
|
+
document.getElementById('infoRecordedUrl').innerHTML = 'Không khả dụng'
|
|
98
|
+
document.getElementById('infoRecordedUrl').href = undefined
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
document.getElementById('infoRecordedUrl').innerHTML = url
|
|
102
|
+
document.getElementById('infoRecordedUrl').href = url
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// inputToken asks access token from the agent
|
|
110
|
+
function inputToken() {
|
|
111
|
+
access_token = prompt('Vui lòng nhập access token')
|
|
112
|
+
localStorage.setItem('access_token', access_token)
|
|
113
|
+
if (access_token) init()
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// fetchRecordedUrl attemps to get the recorded audio url
|
|
117
|
+
// since we dont know when the file is uploaded, we must retry multiple times.
|
|
118
|
+
// this code is only for demo purpose
|
|
119
|
+
function fetchRecordedUrl(callid) {
|
|
120
|
+
// try maximum 4 times
|
|
121
|
+
return new Promise((rs) => {
|
|
122
|
+
fetchConversation(callid).then((convo) => {
|
|
123
|
+
if (convo.call.recorded_audio && convo.call.recorded_audio.url) {
|
|
124
|
+
return rs(convo.call.recorded_audio.url)
|
|
125
|
+
}
|
|
126
|
+
setTimeout(() => {
|
|
127
|
+
fetchConversation(callid).then((convo) => {
|
|
128
|
+
if (convo.call.recorded_audio && convo.call.recorded_audio.url) {
|
|
129
|
+
return rs(convo.call.recorded_audio.url)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
fetchConversation(callid).then((convo) => {
|
|
134
|
+
if (convo.call.recorded_audio && convo.call.recorded_audio.url) {
|
|
135
|
+
return rs(convo.call.recorded_audio.url)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
fetchConversation(callid).then((convo) => {
|
|
140
|
+
if (convo.call.recorded_audio && convo.call.recorded_audio.url) {
|
|
141
|
+
return rs(convo.call.recorded_audio.url)
|
|
142
|
+
}
|
|
143
|
+
rs('unavailable')
|
|
144
|
+
})
|
|
145
|
+
}, 4000)
|
|
146
|
+
})
|
|
147
|
+
}, 4000)
|
|
148
|
+
})
|
|
149
|
+
}, 4000)
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// fetchConversation sends a request API to get the latest conversation
|
|
155
|
+
function fetchConversation(callid) {
|
|
156
|
+
let call = webphone.getCall(callid)
|
|
157
|
+
if (!call) return Promise.resolve(null)
|
|
158
|
+
let convoid = call.conversation_id
|
|
159
|
+
if (!convoid) return Promise.resolve(null)
|
|
160
|
+
return fetch(
|
|
161
|
+
`https://api.subiz.com.vn/4.0/accounts/${accid}/conversations/${convoid}?x-access-token=${access_token}`,
|
|
162
|
+
).then((response) => {
|
|
163
|
+
return response.json()
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function makeCall() {
|
|
168
|
+
if (!webphone) return
|
|
169
|
+
let fromNumber = document.getElementById('fromNumber').value
|
|
170
|
+
let toNumber = document.getElementById('toNumber').value
|
|
171
|
+
//webphone.makeCall(toNumber, fromNumber);
|
|
172
|
+
|
|
173
|
+
webphone.makeCall('0364821895', ['935022139843821727', '842473021368'])
|
|
174
|
+
mycallid = webphone.getCurrentCallId()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
let access_token = localStorage.getItem('access_token')
|
|
178
|
+
if (access_token) init()
|
|
179
|
+
|
|
180
|
+
// update loop
|
|
181
|
+
setInterval(function () {
|
|
182
|
+
if (!webphone) return
|
|
183
|
+
let call = webphone.getCall(mycallid)
|
|
184
|
+
if (!call) return
|
|
185
|
+
document.getElementById('infoCallId').innerHTML = call.call_id
|
|
186
|
+
document.getElementById('infoFromNumber').innerHTML = call.from_number
|
|
187
|
+
document.getElementById('infoConversationId').innerHTML = call.conversation_id
|
|
188
|
+
document.getElementById('infoToNumber').innerHTML = call.to_number
|
|
189
|
+
document.getElementById('infoStatus').innerHTML = call.status
|
|
190
|
+
document.getElementById('infoCreated').innerHTML = new Date(call.started).toLocaleString()
|
|
191
|
+
if (call.status == 'active')
|
|
192
|
+
document.getElementById('infoDuration').innerHTML = Math.floor((Date.now() - call.started) / 1000) || 0
|
|
193
|
+
else {
|
|
194
|
+
document.getElementById('infoDuration').innerHTML = Math.floor((call.ended - call.started) / 1000) || 0
|
|
195
|
+
}
|
|
196
|
+
if (call.hangup_code) document.getElementById('infoHangupCode').innerHTML = call.hangup_code
|
|
197
|
+
}, 500)
|
|
198
|
+
|
|
199
|
+
function hangupCall() {
|
|
200
|
+
if (!webphone) return
|
|
201
|
+
webphone.hangupCall(mycallid)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// expose to window so we can call it in HTML
|
|
205
|
+
window.makeCall = makeCall
|
|
206
|
+
window.hangupCall = hangupCall
|
|
207
|
+
window.inputToken = inputToken
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@subiz/wsclient",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.22",
|
|
4
4
|
"main": "realtime.js",
|
|
5
5
|
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
6
7
|
"test": "node realtime.test.js"
|
|
7
8
|
},
|
|
8
9
|
"repository": {
|
|
@@ -14,10 +15,11 @@
|
|
|
14
15
|
"homepage": "https://github.com/subiz/wsclient#readme",
|
|
15
16
|
"description": "",
|
|
16
17
|
"devDependencies": {
|
|
17
|
-
"tape": "^5.7.
|
|
18
|
+
"tape": "^5.7.3",
|
|
19
|
+
"vite": "^5.0.4",
|
|
20
|
+
"vite-plugin-commonjs": "^0.10.1"
|
|
18
21
|
},
|
|
19
22
|
"dependencies": {
|
|
20
|
-
"@subiz/flow": "^1.1.0"
|
|
21
|
-
"request": "^2.88.2"
|
|
23
|
+
"@subiz/flow": "^1.1.0"
|
|
22
24
|
}
|
|
23
25
|
}
|
package/vite.config.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {defineConfig} from 'vite'
|
|
2
|
+
import commonjs from 'vite-plugin-commonjs'
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [
|
|
6
|
+
commonjs({
|
|
7
|
+
filter(id) {
|
|
8
|
+
if (id.includes('node_modules/redux-storage/build-es')) {
|
|
9
|
+
return true
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
}),
|
|
13
|
+
// Other plugins
|
|
14
|
+
],
|
|
15
|
+
// Other configurations
|
|
16
|
+
})
|
package/webphone.js
CHANGED
|
@@ -56,6 +56,31 @@ function WebPhone(access_token, realtime) {
|
|
|
56
56
|
},
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
+
var _numbers = []
|
|
60
|
+
var _fetchNumbers = function () {
|
|
61
|
+
xhrsend(
|
|
62
|
+
'get',
|
|
63
|
+
`https://api.subiz.com.vn/4.0/integrations?x-access-token=${access_token}`,
|
|
64
|
+
undefined,
|
|
65
|
+
function (body, code) {
|
|
66
|
+
setTimeout(_fetchNumbers, 120_000)
|
|
67
|
+
if (code != 200) return
|
|
68
|
+
var integrations = parseJSON(body)
|
|
69
|
+
if (!integrations || !integrations.length) return
|
|
70
|
+
var numbers = []
|
|
71
|
+
for (var i = 0; i < integrations.length; i++) {
|
|
72
|
+
var integration = integrations[i] || {}
|
|
73
|
+
if (integration.connector_type == 'call' && integration.state == 'activated') {
|
|
74
|
+
var number = integration.id.split('.')[1]
|
|
75
|
+
numbers.push(number)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
_numbers = numbers
|
|
79
|
+
},
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
_fetchNumbers()
|
|
83
|
+
|
|
59
84
|
this.onEvent = function (cb) {
|
|
60
85
|
return pubsub.on('event', cb)
|
|
61
86
|
}
|
|
@@ -129,23 +154,54 @@ function WebPhone(access_token, realtime) {
|
|
|
129
154
|
}
|
|
130
155
|
|
|
131
156
|
var base = '0123456789abcdef'
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
'
|
|
143
|
-
|
|
157
|
+
// makeCall(number, [] || undefined || '', streamPm) // fallback all number
|
|
158
|
+
// makeCall(number, ['a','b','c'], streamPm) // fallback specific number
|
|
159
|
+
// makeCall(number, function() {}, streamPm) //
|
|
160
|
+
this.makeCall = function (number, fromnumbers, streamPm, _a, _b, _state) {
|
|
161
|
+
if (!_state) _state = {}
|
|
162
|
+
if (!fromnumbers || (Array.isArray(fromnumbers) && fromnumbers.length == 0)) {
|
|
163
|
+
fromnumbers = _numbers
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (typeof fromnumbers === 'string' || fromnumbers instanceof String || typeof value === 'number') {
|
|
167
|
+
fromnumbers = [fromnumbers + '']
|
|
168
|
+
}
|
|
144
169
|
|
|
170
|
+
// '11edc52b-2918-4d71-9058-f7285e29d894'
|
|
171
|
+
var callid = `${randomString(8, base)}-${randomString(4, base)}-${randomString(4, base)}`
|
|
172
|
+
callid += `-${randomString(4, base)}-${randomString(12, base)}`
|
|
145
173
|
current_call_id = callid
|
|
146
174
|
|
|
147
175
|
if (!streamPm) streamPm = getMicroPermissions()
|
|
148
|
-
|
|
176
|
+
|
|
177
|
+
let callnumber = chooseCallNumber(fromnumbers, _state.called)
|
|
178
|
+
if (!callnumber)
|
|
179
|
+
return (
|
|
180
|
+
_state.last_result || {
|
|
181
|
+
start: Date.now(),
|
|
182
|
+
ended: Date.now(),
|
|
183
|
+
direction: 'outbound',
|
|
184
|
+
account_id: accid,
|
|
185
|
+
to_number: number,
|
|
186
|
+
error: 'out_of_number',
|
|
187
|
+
id: callid,
|
|
188
|
+
hangup_code: 'failure',
|
|
189
|
+
status: 'ended',
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
return webrtcconn.makeCall(number, callnumber, streamPm, callid).then((out) => {
|
|
193
|
+
if (out.error) return out
|
|
194
|
+
if (out.hangup_code == 'cancel') return out
|
|
195
|
+
if (out.answered > 0) return out
|
|
196
|
+
|
|
197
|
+
if (out.hangup_code == 'terminated' || out.hangup_code == 'failure' || out.hangup_code == 'congestion') {
|
|
198
|
+
if (!_state.called) _state.called = {}
|
|
199
|
+
_state.called[callnumber] = true
|
|
200
|
+
_state.last_result = out
|
|
201
|
+
return this.makeCall(number, fromnumbers, streamPm, _a, _b, _state)
|
|
202
|
+
}
|
|
203
|
+
return out
|
|
204
|
+
})
|
|
149
205
|
}
|
|
150
206
|
|
|
151
207
|
var current_call_id = ''
|
|
@@ -183,3 +239,14 @@ function WebPhone(access_token, realtime) {
|
|
|
183
239
|
|
|
184
240
|
window.SbzWebPhone1 = WebPhone
|
|
185
241
|
module.exports = WebPhone
|
|
242
|
+
|
|
243
|
+
function chooseCallNumber(numbers, called) {
|
|
244
|
+
if (!called) called = {}
|
|
245
|
+
if (!numbers || !numbers.length) return ''
|
|
246
|
+
for (var i = 0; i < numbers.length; i++) {
|
|
247
|
+
var number = numbers[i]
|
|
248
|
+
if (called[number]) continue
|
|
249
|
+
return number
|
|
250
|
+
}
|
|
251
|
+
return ''
|
|
252
|
+
}
|
package/webrtc.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var flow = require('@subiz/flow')
|
|
2
|
-
var {xhrsend, randomString} = require('./common.js')
|
|
2
|
+
var {logError, xhrsend, randomString} = require('./common.js')
|
|
3
3
|
const servers = {
|
|
4
4
|
iceServers: [{urls: ['stun:stun.l.google.com:19302']}],
|
|
5
5
|
}
|
|
@@ -168,6 +168,7 @@ function WebRTCConn(options) {
|
|
|
168
168
|
}
|
|
169
169
|
env.setInterval(cleanEndedCall, 120000)
|
|
170
170
|
|
|
171
|
+
let endcallCb = {}
|
|
171
172
|
let publish = (ev) => {
|
|
172
173
|
if (ev.type == 'call_ended') {
|
|
173
174
|
let callid = ev.data && ev.data.call_info && ev.data.call_info.call_id
|
|
@@ -181,6 +182,13 @@ function WebRTCConn(options) {
|
|
|
181
182
|
// do not clean up this now or some functions cannot work after the call ended
|
|
182
183
|
// DONOT: delete call2Connection[callid]
|
|
183
184
|
delete streams[connId]
|
|
185
|
+
|
|
186
|
+
Object.keys(endcallCb).map((callid) => {
|
|
187
|
+
let arr = endcallCb[callid]
|
|
188
|
+
if (!arr || arr.length == 0 || !arr.map) return
|
|
189
|
+
arr.map((cb) => cb())
|
|
190
|
+
delete endcallCb[callid]
|
|
191
|
+
})
|
|
184
192
|
}
|
|
185
193
|
|
|
186
194
|
let direction = ev.data && ev.data.call_info && ev.data.call_info.direction
|
|
@@ -291,7 +299,10 @@ function WebRTCConn(options) {
|
|
|
291
299
|
peer.setLocalDescription(answer).then(() => {
|
|
292
300
|
let url = `${apiUrl}finish?x-access-token=${access_token}&connection_id=${connId}&version=2&negotiation_id=${negid}`
|
|
293
301
|
callAPI('post', url, jsonify(answer), function (body, code) {
|
|
294
|
-
if (code !== 200)
|
|
302
|
+
if (code !== 200) {
|
|
303
|
+
logError(accid, agid, body, 'finish:295')
|
|
304
|
+
return console.error('CANNOT FINISH', body)
|
|
305
|
+
}
|
|
295
306
|
return
|
|
296
307
|
})
|
|
297
308
|
}),
|
|
@@ -303,7 +314,7 @@ function WebRTCConn(options) {
|
|
|
303
314
|
if (!callid) return
|
|
304
315
|
let type = ev.type
|
|
305
316
|
if (type === 'call_invited') {
|
|
306
|
-
if (
|
|
317
|
+
if (agid == call_info.from_number) return // ignore our own invite
|
|
307
318
|
serverCalls[callid] = call_info
|
|
308
319
|
return publish(ev)
|
|
309
320
|
}
|
|
@@ -359,6 +370,7 @@ function WebRTCConn(options) {
|
|
|
359
370
|
if (campaignid) url += `&campaign_id=${campaignid}`
|
|
360
371
|
callAPI('post', url, undefined, (body, code) => {
|
|
361
372
|
if (code != 200) {
|
|
373
|
+
logError(accid, agid, body, 'makeCall:365')
|
|
362
374
|
delete dialingRequest[callid]
|
|
363
375
|
endRequest[callid] = env.Date.now()
|
|
364
376
|
publish({created: env.Date.now(), type: 'call_ended', data: {call_info: {call_id: callid}}})
|
|
@@ -367,7 +379,11 @@ function WebRTCConn(options) {
|
|
|
367
379
|
body = parseJSON(body)
|
|
368
380
|
if (body) serverCalls[callid] = body
|
|
369
381
|
delete dialingRequest[callid]
|
|
370
|
-
|
|
382
|
+
|
|
383
|
+
let call = this.matchCall(callid)
|
|
384
|
+
if (call && call.status == 'ended') return rs(call)
|
|
385
|
+
if (!endcallCb[callid]) endcallCb[callid] = []
|
|
386
|
+
endcallCb[callid].push(() => rs(this.matchCall(callid)))
|
|
371
387
|
})
|
|
372
388
|
})
|
|
373
389
|
})
|
|
@@ -385,6 +401,7 @@ function WebRTCConn(options) {
|
|
|
385
401
|
serverCalls[callid] = call
|
|
386
402
|
}
|
|
387
403
|
call.status = 'ended'
|
|
404
|
+
call.hangup_code = 'cancel'
|
|
388
405
|
call.ended = env.Date.now()
|
|
389
406
|
publish({created: env.Date.now(), type: 'call_ended', data: {call_info: {call_id: callid}}})
|
|
390
407
|
callAPI('post', `${apiUrl}hangup?x-access-token=${access_token}&call_id=${callid}`)
|
|
@@ -395,6 +412,7 @@ function WebRTCConn(options) {
|
|
|
395
412
|
initSession((err, connId) => {
|
|
396
413
|
let url = `${apiUrl}listen?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}`
|
|
397
414
|
callAPI('post', url, undefined, (body, code) => {
|
|
415
|
+
if (code != 200) logError(accid, agid, body, 'listenCall:399')
|
|
398
416
|
collect('listen_call', callid, env.Date.now() - start)
|
|
399
417
|
return code != 200 ? cb(body) : cb()
|
|
400
418
|
})
|
|
@@ -408,10 +426,9 @@ function WebRTCConn(options) {
|
|
|
408
426
|
streams[connId] = stream
|
|
409
427
|
let url = `${apiUrl}listen?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}&talk=true`
|
|
410
428
|
callAPI('post', url, undefined, (body, code) => {
|
|
429
|
+
if (code != 200) logError(accid, agid, body, 'joinCall:413')
|
|
411
430
|
collect('join_call', callid, env.Date.now() - start)
|
|
412
431
|
if (code != 200) return cb(body, connId)
|
|
413
|
-
console.timeEnd('makecall' + callid)
|
|
414
|
-
if (code != 200) return cb(body, connId)
|
|
415
432
|
checkIceConnected(peers[connId], () => cb(undefined, connId))
|
|
416
433
|
})
|
|
417
434
|
})
|
|
@@ -436,6 +453,7 @@ function WebRTCConn(options) {
|
|
|
436
453
|
callAPI('post', url, undefined, (body, code) => {
|
|
437
454
|
body = parseJSON(body)
|
|
438
455
|
if (code != 200) {
|
|
456
|
+
logError(accid, agid, body, 'answerCall:442')
|
|
439
457
|
delete answerRequest[callid]
|
|
440
458
|
endRequest[callid] = now
|
|
441
459
|
publish({created: env.Date.now(), type: 'call_ended', data: {call_info: {call_id: callid}}})
|
|
@@ -454,7 +472,8 @@ function WebRTCConn(options) {
|
|
|
454
472
|
if (!connId) return rs({body: {}})
|
|
455
473
|
let url = `${apiUrl}dtml?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}&key={key}`
|
|
456
474
|
initSession(() =>
|
|
457
|
-
callAPI('post', url, undefined, (body,
|
|
475
|
+
callAPI('post', url, undefined, (body, code) => {
|
|
476
|
+
if (code != 200) logError(accid, agid, body, 'sendDtmf:463')
|
|
458
477
|
body = parseJSON(body)
|
|
459
478
|
return code != 200 ? rs({error: body}) : rs({body})
|
|
460
479
|
}),
|
|
@@ -469,7 +488,8 @@ function WebRTCConn(options) {
|
|
|
469
488
|
let url = `${apiUrl}refer?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}&number={tonumber}`
|
|
470
489
|
initSession(() =>
|
|
471
490
|
callAPI('post', url, undefined, (body, code) => {
|
|
472
|
-
|
|
491
|
+
if (code != 200) logError(accid, agid, body, 'transferCall:478')
|
|
492
|
+
collect('transfer_call', callid, env.Date.now() - start)
|
|
473
493
|
body = parseJSON(body)
|
|
474
494
|
return code != 200 ? rs({error: body}) : rs({body})
|
|
475
495
|
}),
|