@subiz/wsclient 3.1.9 → 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/common.js +66 -0
- package/package.json +2 -2
- package/realtime.js +1 -68
- package/webrtc.js +353 -0
- package/index.js +0 -1
package/common.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
function dofetch(method, url, body, cb) {
|
|
2
|
+
let headers = {}
|
|
3
|
+
if (body) headers['content-type'] = 'text/plain'
|
|
4
|
+
fetch(url, {method: method, headers: headers, body: body})
|
|
5
|
+
.then((res) => res.text().then((text) => cb && cb(text, res.status)))
|
|
6
|
+
.catch((err) => cb && cb(err, -1))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// xhrsend sends an HTTP request
|
|
10
|
+
var xhrsend = function (method, url, body, cb) {
|
|
11
|
+
if (typeof XMLHttpRequest === 'undefined') return dofetch(method, url, body, cb)
|
|
12
|
+
var request = new XMLHttpRequest()
|
|
13
|
+
request.onreadystatechange = function (e) {
|
|
14
|
+
if (request.readyState !== 4) return
|
|
15
|
+
cb && cb(request.responseText, request.status)
|
|
16
|
+
cb = undefined // dont call cb anymore
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
request.onerror = function () {
|
|
20
|
+
cb && cb(request.responseText, -1) // network error
|
|
21
|
+
cb = undefined // dont call cb anymore
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
request.open(method, url)
|
|
25
|
+
if (body) request.setRequestHeader('content-type', 'text/plain')
|
|
26
|
+
request.send(body)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// JSON.parse without exeption
|
|
30
|
+
// return undefined when parsing invalid JSON string
|
|
31
|
+
function parseJSON(str) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(str)
|
|
34
|
+
} catch (e) {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function Pubsub() {
|
|
38
|
+
// holds all subscriptions for all topics
|
|
39
|
+
// example {
|
|
40
|
+
// topic1: [cb1, cb2],
|
|
41
|
+
// topic2: [cb3, cb4],
|
|
42
|
+
// }
|
|
43
|
+
var listeners = {}
|
|
44
|
+
|
|
45
|
+
// emit notifies any callback functions that previously
|
|
46
|
+
// subscribed to the topic (by the on function)
|
|
47
|
+
this.emit = function (topic) {
|
|
48
|
+
// skip the first item in arguments, which is the topic
|
|
49
|
+
var args = []
|
|
50
|
+
for (var i = 1; i < arguments.length; i++) args.push(arguments[i])
|
|
51
|
+
|
|
52
|
+
var cbs = listeners[topic]
|
|
53
|
+
if (!cbs) return
|
|
54
|
+
for (var i = 0; i < cbs.length; i++) cbs[i].apply(undefined, args)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// register a callback function for a topic
|
|
58
|
+
// when something is sent to the topic (by the emit function),
|
|
59
|
+
// the callback function will be called
|
|
60
|
+
this.on = function (topic, cb) {
|
|
61
|
+
if (!listeners[topic]) listeners[topic] = []
|
|
62
|
+
listeners[topic].push(cb)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {xhrsend, parseJSON, Pubsub}
|
package/package.json
CHANGED
package/realtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var flow = require('@subiz/flow')
|
|
2
|
+
var {xhrsend, parseJSON, Pubsub} = require('./common.js')
|
|
2
3
|
|
|
3
4
|
// Conn represents a realtime connection between an user and the realtime
|
|
4
5
|
// server (https://rt-0.subiz.com)
|
|
@@ -212,74 +213,6 @@ function Realtime(apiUrls, credential, callAPI) {
|
|
|
212
213
|
reconnect()
|
|
213
214
|
}
|
|
214
215
|
|
|
215
|
-
var dofetch = function (method, url, body, cb) {
|
|
216
|
-
let headers = {}
|
|
217
|
-
if (body) headers['content-type'] = 'text/plain'
|
|
218
|
-
fetch(url, {
|
|
219
|
-
method: method,
|
|
220
|
-
headers: headers,
|
|
221
|
-
body: body,
|
|
222
|
-
})
|
|
223
|
-
.then((res) => res.text().then((text) => cb && cb(text, res.status)))
|
|
224
|
-
.catch((err) => cb && cb(err, -1))
|
|
225
|
-
}
|
|
226
|
-
// xhrsend sends an HTTP request
|
|
227
|
-
var xhrsend = function (method, url, body, cb) {
|
|
228
|
-
if (typeof XMLHttpRequest === 'undefined') return dofetch(method, url, body, cb)
|
|
229
|
-
var request = new XMLHttpRequest()
|
|
230
|
-
request.onreadystatechange = function (e) {
|
|
231
|
-
if (request.readyState !== 4) return
|
|
232
|
-
cb && cb(request.responseText, request.status)
|
|
233
|
-
cb = undefined // dont call cb anymore
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
request.onerror = function () {
|
|
237
|
-
cb && cb(request.responseText, -1) // network error
|
|
238
|
-
cb = undefined // dont call cb anymore
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
request.open(method, url)
|
|
242
|
-
if (body) request.setRequestHeader('content-type', 'text/plain')
|
|
243
|
-
request.send(body)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// JSON.parse without exeption
|
|
247
|
-
// return undefined when parsing invalid JSON string
|
|
248
|
-
function parseJSON(str) {
|
|
249
|
-
try {
|
|
250
|
-
return JSON.parse(str)
|
|
251
|
-
} catch (e) {}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function Pubsub() {
|
|
255
|
-
// holds all subscriptions for all topics
|
|
256
|
-
// example {
|
|
257
|
-
// topic1: [cb1, cb2],
|
|
258
|
-
// topic2: [cb3, cb4],
|
|
259
|
-
// }
|
|
260
|
-
var listeners = {}
|
|
261
|
-
|
|
262
|
-
// emit notifies any callback functions that previously
|
|
263
|
-
// subscribed to the topic (by the on function)
|
|
264
|
-
this.emit = function (topic) {
|
|
265
|
-
// skip the first item in arguments, which is the topic
|
|
266
|
-
var args = []
|
|
267
|
-
for (var i = 1; i < arguments.length; i++) args.push(arguments[i])
|
|
268
|
-
|
|
269
|
-
var cbs = listeners[topic]
|
|
270
|
-
if (!cbs) return
|
|
271
|
-
for (var i = 0; i < cbs.length; i++) cbs[i].apply(undefined, args)
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// register a callback function for a topic
|
|
275
|
-
// when something is sent to the topic (by the emit function),
|
|
276
|
-
// the callback function will be called
|
|
277
|
-
this.on = function (topic, cb) {
|
|
278
|
-
if (!listeners[topic]) listeners[topic] = []
|
|
279
|
-
listeners[topic].push(cb)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
216
|
// calcNextBackoff returns number of seconds we must wait
|
|
284
217
|
// before sending a next request
|
|
285
218
|
// the results is determind based on exponential backoff algorithm
|
package/webrtc.js
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
var flow = require('@subiz/flow')
|
|
2
|
+
var {xhrsend} = require('./common.js')
|
|
3
|
+
const servers = {
|
|
4
|
+
iceServers: [{urls: ['stun:stun.l.google.com:19302?transport=tcp']}],
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// let connId = 'webrtc' + sb.randomString(20)
|
|
8
|
+
// env = {RTCPeerConnection, RTCIceCandidate, RTCSessionDescription, setTimeout, setInterval, jsonify, parseJSON}
|
|
9
|
+
// setting env._is_webrtc_local to true for local testing
|
|
10
|
+
function WebRTCConn(options) {
|
|
11
|
+
let {apiUrl, access_token, accid, agid, connId, realtime, callAPI, env, onTrack, onEvent} = options
|
|
12
|
+
if (!apiUrl) {
|
|
13
|
+
apiUrl = 'https://callcenter.subiz.com.vn/wc/'
|
|
14
|
+
if (env._is_webrtc_local) apiUrl = 'http://localhost:8008/'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
callAPI = callAPI || xhrsend // allow hook
|
|
18
|
+
|
|
19
|
+
let parseJSON = (str) => {
|
|
20
|
+
try {
|
|
21
|
+
return env.JSON.parse(str)
|
|
22
|
+
} catch (e) {}
|
|
23
|
+
}
|
|
24
|
+
let jsonify = (str) => {
|
|
25
|
+
try {
|
|
26
|
+
return env.JSON.stringify(str)
|
|
27
|
+
} catch (e) {}
|
|
28
|
+
}
|
|
29
|
+
let currentStream
|
|
30
|
+
let pendingStream
|
|
31
|
+
let activeCalls = {}
|
|
32
|
+
|
|
33
|
+
let dialingRequest = {}
|
|
34
|
+
let endRequest = {}
|
|
35
|
+
|
|
36
|
+
let subscribe = realtime.subscribe([
|
|
37
|
+
`webrtc.account.${accid}.agent.${agid}`,
|
|
38
|
+
`webrtc.account.${accid}.agent.${agid}.webrtc_connection.${connId}`,
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
let peer
|
|
42
|
+
// makeSure only returns when stream is added and negotiation is finished
|
|
43
|
+
// its safe to call this function multiple times at once
|
|
44
|
+
let makeSure = (cb) => {
|
|
45
|
+
initConnection()
|
|
46
|
+
subscribe.then(cb)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let checkIceConnected = (cb) =>
|
|
50
|
+
flow.loop(
|
|
51
|
+
() =>
|
|
52
|
+
new env.Promise((rs) => {
|
|
53
|
+
let pass = peer && (peer.iceConnectionState == 'connected' || peer.iceConnectionState == 'completed')
|
|
54
|
+
if (!pass) return env.setTimeout((_) => rs(true), 100) // keep looping util connected
|
|
55
|
+
cb && cb()
|
|
56
|
+
rs(false)
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
let readyToSendCandidateResolve
|
|
61
|
+
let readyToSendCandidate = new env.Promise((rs) => (readyToSendCandidateResolve = rs))
|
|
62
|
+
|
|
63
|
+
let initConnection = () => {
|
|
64
|
+
let needConnect =
|
|
65
|
+
!peer ||
|
|
66
|
+
peer.connectionState === 'disconnected' ||
|
|
67
|
+
peer.connectionState === 'closed' ||
|
|
68
|
+
peer.connectionState === 'failed'
|
|
69
|
+
|
|
70
|
+
if (!needConnect) return
|
|
71
|
+
peer = new env.RTCPeerConnection(servers)
|
|
72
|
+
let mypeer = peer
|
|
73
|
+
window.peer = peer
|
|
74
|
+
|
|
75
|
+
readyToSendCandidate = new env.Promise((rs) => (readyToSendCandidateResolve = rs))
|
|
76
|
+
|
|
77
|
+
peer.ontrack = (event) => {
|
|
78
|
+
if (peer != mypeer) return // outdated
|
|
79
|
+
onTrack && onTrack(event)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
peer.onicecandidate = (event) => {
|
|
83
|
+
if (!event.candidate) return
|
|
84
|
+
if (peer != mypeer) return // outdated
|
|
85
|
+
let url = `${apiUrl}ice-candidates?x-access-token=${access_token}&connection_id=${connId}`
|
|
86
|
+
callAPI('post', url, jsonify(event.candidate))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
peer.onnegotiationneeded = (ev) => console.log('NNNEED RE-NEGOTIATION', ev)
|
|
90
|
+
console.log('NEW PERR')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let stopMic = () => {
|
|
94
|
+
console.log('00000000STOP TRACK', currentStream)
|
|
95
|
+
return
|
|
96
|
+
currentStream &&
|
|
97
|
+
currentStream.getTracks().forEach((track) => {
|
|
98
|
+
track.enabled = false
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// auto tear down when byte received is zero
|
|
103
|
+
// use to clear peer when there's no byte sent around 100 seconds
|
|
104
|
+
let receivedBytes = []
|
|
105
|
+
let check = () => {
|
|
106
|
+
let url = `${apiUrl}calls?x-access-token=${access_token}&connection_id=${connId}`
|
|
107
|
+
callAPI('get', url, undefined, function (body, code) {
|
|
108
|
+
if (code == 200) {
|
|
109
|
+
body = parseJSON(body) || []
|
|
110
|
+
activeCalls = {}
|
|
111
|
+
body.map && body.map((call) => (activeCalls[call.call_id] = call))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!peer) return
|
|
115
|
+
peer.getStats(null).then((stats) => {
|
|
116
|
+
let statsOutput = ''
|
|
117
|
+
let receivedByte = 0
|
|
118
|
+
stats.forEach((report) => {
|
|
119
|
+
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
|
|
120
|
+
receivedByte += report.bytesReceived || 0
|
|
121
|
+
}
|
|
122
|
+
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
|
123
|
+
receivedByte += report.bytesReceived || 0
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
receivedBytes.push(receivedByte)
|
|
127
|
+
while (receivedBytes.length > 10) receivedBytes.shift()
|
|
128
|
+
let diff = receivedBytes.filter((n) => n != receivedByte)
|
|
129
|
+
if (diff.length == 0 && receivedBytes.length >= 10 && Object.keys(activeCalls).length == 0) {
|
|
130
|
+
if (Object.keys(activeCalls).length == 0) stopMic()
|
|
131
|
+
// could tear down
|
|
132
|
+
peer.close()
|
|
133
|
+
peer = undefined
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
check()
|
|
139
|
+
env.setInterval(check, 30000)
|
|
140
|
+
|
|
141
|
+
let publish = (ev) => onEvent && onEvent(ev)
|
|
142
|
+
|
|
143
|
+
this.matchCall = (callid) => {
|
|
144
|
+
if (!callid || callid == '*' || callid == '-') {
|
|
145
|
+
let calls = Object.assign({}, activeCalls)
|
|
146
|
+
Object.keys(dialingRequest).map((callid) => {
|
|
147
|
+
let call = activeCalls[callid]
|
|
148
|
+
if (!call && dialingRequest[callid]) {
|
|
149
|
+
calls[callid] = dialingRequest[callid]
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
return calls
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let call = activeCalls[callid]
|
|
156
|
+
if (endRequest[callid] && call && call.status != 'ended') {
|
|
157
|
+
return Object.assign({}, call, {
|
|
158
|
+
call_id: callid,
|
|
159
|
+
ended: endRequest[callid],
|
|
160
|
+
status: 'ended',
|
|
161
|
+
hangup_code: 'cancel',
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
if (!call && dialingRequest[callid]) return dialingRequest[callid]
|
|
165
|
+
return call
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
realtime.onEvent((ev) => {
|
|
169
|
+
if (!ev || !ev.type || !ev.data) return
|
|
170
|
+
// if (ev.type.startsWith('call')) console.log('EEEEEEE', ev)
|
|
171
|
+
if (env._is_webrtc_local && ev.user_id != '8') return // local
|
|
172
|
+
if (!env._is_webrtc_local && ev.user_id != '9') return // prod
|
|
173
|
+
|
|
174
|
+
if (ev.type == 'webrtc_candidate_added') {
|
|
175
|
+
let ice_candidate = ev.data.ice_candidate
|
|
176
|
+
if (!ice_candidate) return
|
|
177
|
+
return readyToSendCandidate.then(() => peer && peer.addIceCandidate(parseJSON(ice_candidate)))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (ev.type == 'webrtc_negotiation_offered') {
|
|
181
|
+
console.log('RT/RECEIVE OFFERED NEGOTIATION', ev)
|
|
182
|
+
if (!peer) return
|
|
183
|
+
let negid = ev.id
|
|
184
|
+
if (!ev.data.ice_candidate) return
|
|
185
|
+
let offer = parseJSON(ev.data.ice_candidate)
|
|
186
|
+
|
|
187
|
+
peer
|
|
188
|
+
.setRemoteDescription(offer)
|
|
189
|
+
.then(() => {
|
|
190
|
+
readyToSendCandidateResolve()
|
|
191
|
+
if (pendingStream && pendingStream != currentStream) {
|
|
192
|
+
currentStream = pendingStream
|
|
193
|
+
currentStream.getTracks().forEach((track) => peer.addTrack(track, currentStream))
|
|
194
|
+
}
|
|
195
|
+
pendingStream = undefined
|
|
196
|
+
return peer.createAnswer()
|
|
197
|
+
})
|
|
198
|
+
.then((answer) =>
|
|
199
|
+
peer.setLocalDescription(answer).then(() => {
|
|
200
|
+
let url = `${apiUrl}finish?x-access-token=${access_token}&connection_id=${connId}&version=2&negotiation_id=${negid}`
|
|
201
|
+
callAPI('post', url, jsonify(answer), function (body, code) {
|
|
202
|
+
if (code !== 200) return console.error('CANNOT FINISH', body)
|
|
203
|
+
return
|
|
204
|
+
})
|
|
205
|
+
}),
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let call_info = ev.data.call_info || {}
|
|
210
|
+
let callid = call_info.call_id
|
|
211
|
+
if (!callid) return
|
|
212
|
+
|
|
213
|
+
if (ev.type === 'call_invite_expired') {
|
|
214
|
+
let device_id = call_info.device_id
|
|
215
|
+
if (device_id == connId) return // ignore since we have accepted the call
|
|
216
|
+
publish(Object.assign({}, ev, {type: 'call_ended'})) // fake ended call
|
|
217
|
+
delete activeCalls[callid]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let type = ev.type
|
|
221
|
+
if (type == 'call_ended' || type == 'call_ringing' || type == 'call_joined' || type === 'call_invited') {
|
|
222
|
+
if (ev.type === 'call_invited' && agid == call_info.from_number) return // ignore our own invite
|
|
223
|
+
activeCalls[callid] = call_info
|
|
224
|
+
return publish(ev)
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
this.makeCall = (number, fromnumber, stream) =>
|
|
229
|
+
new env.Promise((rs) => {
|
|
230
|
+
let callid = 'webcall-' + randomString(20)
|
|
231
|
+
let call = {
|
|
232
|
+
account_id: accid,
|
|
233
|
+
call_id: callid,
|
|
234
|
+
device_id: connId,
|
|
235
|
+
direction: 'outbound',
|
|
236
|
+
member_id: agid,
|
|
237
|
+
started: env.Date.now(),
|
|
238
|
+
status: 'dialing',
|
|
239
|
+
to_number: number,
|
|
240
|
+
}
|
|
241
|
+
dialingRequest[callid] = call
|
|
242
|
+
publish({created: env.Date.now(), type: 'call_ringing', data: {call_info: call}}) // fake dialing
|
|
243
|
+
this.joinCall(callid, stream, (err) => {
|
|
244
|
+
if (err) {
|
|
245
|
+
delete dialingRequest[callid]
|
|
246
|
+
endRequest[callid] = env.Date.now()
|
|
247
|
+
publish({
|
|
248
|
+
type: 'call_ended',
|
|
249
|
+
data: {call_info: {call_id: callid, ended: endRequest[callid], status: 'ended', hangup_code: 'error'}},
|
|
250
|
+
})
|
|
251
|
+
return rs({error: err})
|
|
252
|
+
}
|
|
253
|
+
let url = `${apiUrl}call?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}&number=${number}&from_number=${fromnumber}`
|
|
254
|
+
callAPI('post', url, undefined, (body, code) => {
|
|
255
|
+
if (code != 200) {
|
|
256
|
+
delete dialingRequest[callid]
|
|
257
|
+
endRequest[callid] = env.Date.now()
|
|
258
|
+
publish({
|
|
259
|
+
type: 'call_ended',
|
|
260
|
+
data: {call_info: {call_id: callid, ended: endRequest[callid], status: 'ended', hangup_code: 'error'}},
|
|
261
|
+
})
|
|
262
|
+
return rs({error: err})
|
|
263
|
+
}
|
|
264
|
+
body = parseJSON(body)
|
|
265
|
+
if (body) activeCalls[callid] = body
|
|
266
|
+
rs({body})
|
|
267
|
+
})
|
|
268
|
+
})
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
this.hangupCall = (callid) => {
|
|
272
|
+
if (!callid) return
|
|
273
|
+
endRequest[callid] = env.Date.now()
|
|
274
|
+
|
|
275
|
+
let call = activeCalls[callid]
|
|
276
|
+
if (!call) {
|
|
277
|
+
call = {call_id: callid}
|
|
278
|
+
activeCalls[callid] = call
|
|
279
|
+
}
|
|
280
|
+
call.status = 'ended'
|
|
281
|
+
call.ended = env.Date.now()
|
|
282
|
+
callAPI('post', `${apiUrl}hangup?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}`)
|
|
283
|
+
publish({type: 'call_ended', data: {call_info: call}})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.listenCall = (callid, cb) => {
|
|
287
|
+
let url = `${apiUrl}listen?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}`
|
|
288
|
+
makeSure(() => callAPI('post', url, undefined, (body, code) => (code != 200 ? cb(body) : cb())))
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.joinCall = (callid, stream, cb) => {
|
|
292
|
+
console.time('makecall' + callid)
|
|
293
|
+
makeSure(() => {
|
|
294
|
+
pendingStream = stream
|
|
295
|
+
let url = `${apiUrl}listen?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}&talk=true`
|
|
296
|
+
callAPI('post', url, undefined, (body, code) => {
|
|
297
|
+
if (code != 200) return cb(body)
|
|
298
|
+
console.timeEnd('makecall' + callid)
|
|
299
|
+
if (code != 200) return cb(body)
|
|
300
|
+
checkIceConnected(() => cb())
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
this.answerCall = (callid, stream) =>
|
|
306
|
+
new env.Promise((rs) => {
|
|
307
|
+
this.joinCall(callid, stream, (err) => {
|
|
308
|
+
if (err) return rs({error: err})
|
|
309
|
+
let url = `${apiUrl}answer?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}`
|
|
310
|
+
callAPI('post', url, undefined, (body, code) => {
|
|
311
|
+
body = parseJSON(body)
|
|
312
|
+
return code != 200 ? rs({error: body}) : rs({body})
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
this.sendDtmf = (key, callid) =>
|
|
318
|
+
new env.Promise((rs) => {
|
|
319
|
+
if (!callid) return rs({body: {}})
|
|
320
|
+
let url = `${apiUrl}dtml?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}&key={key}`
|
|
321
|
+
makeSure(() =>
|
|
322
|
+
callAPI('post', url, undefined, (body, bode) => {
|
|
323
|
+
body = parseJSON(body)
|
|
324
|
+
return code != 200 ? rs({error: body}) : rs({body})
|
|
325
|
+
}),
|
|
326
|
+
)
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
this.transferCall = (tonumber, callid) =>
|
|
330
|
+
new env.Promise((rs) => {
|
|
331
|
+
if (!callid) return rs({})
|
|
332
|
+
let url = `${apiUrl}refer?x-access-token=${access_token}&connection_id=${connId}&call_id=${callid}&number={tonumber}`
|
|
333
|
+
makeSure(() =>
|
|
334
|
+
callAPI('post', url, undefined, (body, code) => {
|
|
335
|
+
body = parseJSON(body)
|
|
336
|
+
return code != 200 ? rs({error: body}) : rs({body})
|
|
337
|
+
}),
|
|
338
|
+
)
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function randomString(len) {
|
|
343
|
+
var str = ''
|
|
344
|
+
if (!len || len < 1) len = 10
|
|
345
|
+
var asciiKey
|
|
346
|
+
for (var i = 0; i < len; i++) {
|
|
347
|
+
asciiKey = Math.floor(Math.random() * 25 + 97)
|
|
348
|
+
str += String.fromCharCode(asciiKey)
|
|
349
|
+
}
|
|
350
|
+
return str
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports = WebRTCConn
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = require('./realtime.js')
|