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