@telegenta/webclient 3.0.1 → 3.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -53
- package/demos/calldata_available.html +1 -1
- package/demos/event_client.html +1 -1
- package/dist/telegenta.js +112 -30
- package/dist/telegenta.min.js +1 -1
- package/docs/EventServiceNotConnected.html +1 -1
- package/docs/NoActiveSession.html +1 -1
- package/docs/NotAllowed.html +1 -1
- package/docs/Phone.html +1 -1
- package/docs/Session.html +4 -1
- package/docs/UnableToConnect.html +1 -1
- package/docs/data/search.json +1 -1
- package/docs/event-client.js.html +1 -1
- package/docs/exceptions.js.html +3 -2
- package/docs/global.html +3 -3
- package/docs/phone-client.js.html +45 -1
- package/package.json +7 -3
package/README.md
CHANGED
|
@@ -39,63 +39,49 @@ and you are ready to go. The client has no external requirements (like jQuery an
|
|
|
39
39
|
|
|
40
40
|
Before starting, you will need an account on telegenta.com, to create sip accounts and api tokens.
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
Runnable examples live in `demos/`. They load the browser bundle from `../dist/telegenta.min.js`, so
|
|
43
|
+
run `npm run build` first, then serve the directory over https. WebRTC requires a secure context;
|
|
44
|
+
`localhost` counts as secure, but opening a demo as `file://...` does not - media permissions are not
|
|
45
|
+
remembered there, so you will be prompted for the microphone every time.
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
#### Example: phone with a provisioning token
|
|
45
48
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
library /embedded_client/index.html and modify the credentials:
|
|
49
|
+
`demos/phone_client.html` is a complete softphone - start/stop, outbound calls, transfer, listen-in,
|
|
50
|
+
incoming calls and the event client.
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var USERNAME='your username';
|
|
53
|
-
var PASSWORD='your password';
|
|
54
|
-
|
|
55
|
-
window.onload = function () {
|
|
56
|
-
...
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Now open the file (index.html) in your browser. After you have been prompted for permission to use the audio devices,
|
|
60
|
-
you should have a working phone.
|
|
61
|
-
Note: If you are viewing a file using "file://index.html", it is not considered a secure connection by the browser
|
|
62
|
-
and media permissions are not remembered, so you will be prompted every time you open the file.
|
|
63
|
-
However, once you move the file to a secure webserver (https), this will no longer be an issue.
|
|
64
|
-
|
|
65
|
-
#### Example 2: Using SIP Flex accounts
|
|
52
|
+
First create a static API token in the admin interface
|
|
53
|
+
(https://admin.telegenta.com/integration/access-tokens). Open the demo and paste the token in; it then
|
|
66
54
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
For this example we need a backend service and we provide a simple example in PHP using the built in Webserver.
|
|
55
|
+
1. requests a short-lived provisioning token with `GET /api/get-call-session/?id=<account id>`, and
|
|
56
|
+
2. starts the phone with that token:
|
|
70
57
|
|
|
71
|
-
|
|
58
|
+
```
|
|
59
|
+
const opts = {
|
|
60
|
+
// Enable event client
|
|
61
|
+
connectEventService: true,
|
|
72
62
|
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
// Subscribe to detailed information about call after processing
|
|
64
|
+
useCallDataInfoEvent: true,
|
|
75
65
|
|
|
76
|
-
|
|
66
|
+
// Enable incoming calls (you must set a phone number to call the account also)
|
|
67
|
+
register: true,
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
69
|
+
// Configure client with provision token
|
|
70
|
+
provisionToken: data.provision_token,
|
|
71
|
+
};
|
|
81
72
|
|
|
82
|
-
|
|
83
|
-
// It possible to have multiple sip flex accounts with different configurations
|
|
84
|
-
$flex_identification = 'test';
|
|
85
|
-
...
|
|
73
|
+
phoneClient.start(opts);
|
|
86
74
|
```
|
|
87
75
|
|
|
88
|
-
|
|
76
|
+
Issuing the provisioning token from your own backend is the recommended integration: the short-lived
|
|
77
|
+
token is all that reaches the browser, and your API token stays on the server.
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
cd embedded_client
|
|
92
|
-
php -S localhost:9000
|
|
93
|
-
```
|
|
79
|
+
The remaining demos cover narrower cases:
|
|
94
80
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
81
|
+
- `demos/event_client.html` - the event client on its own, without a phone
|
|
82
|
+
- `demos/calldata_available.html` - subscribing to call data after a call completes
|
|
83
|
+
- `demos/add_phone_number.html` - adding a phone number through the API
|
|
84
|
+
- `demos/embedded_config.html` - embedding the admin config pages with a config token
|
|
99
85
|
|
|
100
86
|
## Approaching the integration
|
|
101
87
|
|
|
@@ -106,15 +92,10 @@ Depending on how much flexibility you need, you can choose between the following
|
|
|
106
92
|
We believe in using standard protocols as much as possible and it will work nicely, but you need to address most
|
|
107
93
|
of the issues we listed above in the previous section yourself.
|
|
108
94
|
|
|
109
|
-
2. Use our free library
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
We have made a simple phone with frontend, which can be easily included in your own project. Just grab the
|
|
114
|
-
index.html, style.css and embedded_phone.js and insert it into your own project.
|
|
115
|
-
We named the JS and CSS to try and avoid any potential conflicts.
|
|
116
|
-
You only need to provide it with credentials or provisioning token (as in the example above) from you backend and
|
|
117
|
-
you are set to start calling.
|
|
95
|
+
2. Use our free library and provide your own frontend.
|
|
96
|
+
Load `dist/telegenta.min.js` in the browser (global `Telegenta`) or import the package in your
|
|
97
|
+
bundler, then handle events and use javascript methods like call, hangup, startRecord, etc..
|
|
98
|
+
`demos/phone_client.html` shows the whole flow end to end.
|
|
118
99
|
|
|
119
100
|
## Migrating from 2.x
|
|
120
101
|
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
eventClient = new Telegenta.EventClient(logger);
|
|
46
46
|
|
|
47
47
|
// Connect to event server
|
|
48
|
-
eventClient.connect('wss://
|
|
48
|
+
eventClient.connect('wss://esvc.telegenta.com/').then(function () {
|
|
49
49
|
console.info(`Connected to server`);
|
|
50
50
|
}, function (reason) {
|
|
51
51
|
console.error(`Unable to connect to event service: ${reason}`);
|
package/demos/event_client.html
CHANGED
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
let eventClient;
|
|
54
54
|
let subscriptionId = null;
|
|
55
55
|
const API_URL = 'https://api.telegenta.com/api';
|
|
56
|
-
const EVENT_CLIENT_URL = 'wss://
|
|
56
|
+
const EVENT_CLIENT_URL = 'wss://esvc.telegenta.com/';
|
|
57
57
|
|
|
58
58
|
// Start when DOM is ready
|
|
59
59
|
window.onload = function () {
|
package/dist/telegenta.js
CHANGED
|
@@ -25,23 +25,23 @@ class LogHandler {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
debug(...args) {
|
|
28
|
-
this._doLog(LOGLVL
|
|
28
|
+
this._doLog(LOGLVL.DEBUG, args);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
info(...args) {
|
|
32
|
-
this._doLog(LOGLVL
|
|
32
|
+
this._doLog(LOGLVL.INFO, args);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
warn(...args) {
|
|
36
|
-
this._doLog(LOGLVL
|
|
36
|
+
this._doLog(LOGLVL.WARN, args);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
warning(...args) {
|
|
40
|
-
this._doLog(LOGLVL
|
|
40
|
+
this._doLog(LOGLVL.WARN, args);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
error(...args) {
|
|
44
|
-
this._doLog(LOGLVL
|
|
44
|
+
this._doLog(LOGLVL.ERROR, args);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
_doLog(level, args) {
|
|
@@ -60,19 +60,19 @@ class LogHandler {
|
|
|
60
60
|
this.extHandler(LOGLVLSTR[level], args);
|
|
61
61
|
} else {
|
|
62
62
|
switch (level) {
|
|
63
|
-
case LOGLVL
|
|
63
|
+
case LOGLVL.DEBUG:
|
|
64
64
|
console.debug(args);
|
|
65
65
|
break;
|
|
66
66
|
|
|
67
|
-
case LOGLVL
|
|
67
|
+
case LOGLVL.INFO:
|
|
68
68
|
console.info(args);
|
|
69
69
|
break;
|
|
70
70
|
|
|
71
|
-
case LOGLVL
|
|
71
|
+
case LOGLVL.WARN:
|
|
72
72
|
console.warn(args);
|
|
73
73
|
break;
|
|
74
74
|
|
|
75
|
-
case LOGLVL
|
|
75
|
+
case LOGLVL.ERROR:
|
|
76
76
|
console.error(args);
|
|
77
77
|
break;
|
|
78
78
|
|
|
@@ -91,7 +91,7 @@ class LogHandler {
|
|
|
91
91
|
* @property {number} INFO 2 Informational messages (default)
|
|
92
92
|
* @property {number} DEBUG 3 Debug messages
|
|
93
93
|
*/
|
|
94
|
-
const LOGLVL
|
|
94
|
+
const LOGLVL = {
|
|
95
95
|
DEBUG: 3,
|
|
96
96
|
INFO: 2,
|
|
97
97
|
WARN: 1,
|
|
@@ -168,10 +168,11 @@ class InvalidParameter extends Error {
|
|
|
168
168
|
|
|
169
169
|
/**
|
|
170
170
|
* Exception raised if trying to perform actions that require active session
|
|
171
|
+
* @property message - Error message
|
|
171
172
|
*/
|
|
172
173
|
class NoActiveSession extends Error {
|
|
173
|
-
constructor() {
|
|
174
|
-
super();
|
|
174
|
+
constructor(message) {
|
|
175
|
+
super(message);
|
|
175
176
|
this.name = 'NoActiveSession';
|
|
176
177
|
}
|
|
177
178
|
}
|
|
@@ -291,6 +292,43 @@ function browserIsEdge() {
|
|
|
291
292
|
return (navigator.userAgent.indexOf("Edge") !== -1)
|
|
292
293
|
}
|
|
293
294
|
|
|
295
|
+
// SIP header names are RFC 3261 tokens
|
|
296
|
+
const HEADER_NAME_RE = /^[A-Za-z0-9\-.!%*_+`'~]+$/;
|
|
297
|
+
|
|
298
|
+
const HTML_ENTITIES = {
|
|
299
|
+
'&': '&',
|
|
300
|
+
'<': '<',
|
|
301
|
+
'>': '>',
|
|
302
|
+
'"': '"',
|
|
303
|
+
"'": ''',
|
|
304
|
+
'\r': ' ',
|
|
305
|
+
'\n': ' ',
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Build custom SIP header lines from a { name: value } map.
|
|
309
|
+
// Names must be tokens - a bad one is always a programming error, so it throws.
|
|
310
|
+
// Values are HTML encoded rather than rejected: that strips the CR/LF which
|
|
311
|
+
// would otherwise let a value inject further headers, while still letting
|
|
312
|
+
// punctuation through.
|
|
313
|
+
function buildCustomHeaders(setHeader) {
|
|
314
|
+
if (!setHeader) {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (typeof setHeader !== 'object' || Array.isArray(setHeader)) {
|
|
319
|
+
throw new TypeError(`Invalid setHeader argument: expected an object, got ${typeof setHeader}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return Object.entries(setHeader).map(([name, value]) => {
|
|
323
|
+
if (!HEADER_NAME_RE.test(name)) {
|
|
324
|
+
throw new TypeError(`Invalid SIP header name: "${name}"`);
|
|
325
|
+
}
|
|
326
|
+
// Single pass, so an encoded '&' is not encoded again.
|
|
327
|
+
const encoded = String(value).replace(/[&<>"'\r\n]/g, (c) => HTML_ENTITIES[c]);
|
|
328
|
+
return `${name}: ${encoded}`;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
294
332
|
// Request timeout after 10 sec
|
|
295
333
|
const REQUEST_TIMEOUT = 10000;
|
|
296
334
|
|
|
@@ -348,7 +386,7 @@ class Request {
|
|
|
348
386
|
class Requestor extends EventEmitter {
|
|
349
387
|
constructor(logger = null) {
|
|
350
388
|
super();
|
|
351
|
-
this.log = logger ? logger : new LogHandler(LOGLVL
|
|
389
|
+
this.log = logger ? logger : new LogHandler(LOGLVL.INFO);
|
|
352
390
|
this._pendingRequests = [];
|
|
353
391
|
}
|
|
354
392
|
|
|
@@ -363,13 +401,13 @@ class Requestor extends EventEmitter {
|
|
|
363
401
|
let msg;
|
|
364
402
|
try {
|
|
365
403
|
msg = JSON.parse(message);
|
|
366
|
-
} catch
|
|
404
|
+
} catch {
|
|
367
405
|
this.log.warning("Unable to parse message: " + message);
|
|
368
406
|
return;
|
|
369
407
|
}
|
|
370
408
|
|
|
371
409
|
// Validate msg fields
|
|
372
|
-
if (!
|
|
410
|
+
if (!Object.hasOwn(msg, "type") || !Object.hasOwn(msg, "id")) {
|
|
373
411
|
this.log.warning(`Invalid message received: ${msg} - missing type or id`);
|
|
374
412
|
return;
|
|
375
413
|
}
|
|
@@ -522,7 +560,7 @@ class Requestor extends EventEmitter {
|
|
|
522
560
|
// Keep alive in seconds
|
|
523
561
|
const PING_INTERVAL = 50;
|
|
524
562
|
|
|
525
|
-
const ES_DEFAULT_SERVER_URL = 'wss://
|
|
563
|
+
const ES_DEFAULT_SERVER_URL = 'wss://esvc.telegenta.com/';
|
|
526
564
|
|
|
527
565
|
// Starting retry interval
|
|
528
566
|
const RETRY_INTERVAL_START = 1 + Math.random();
|
|
@@ -620,7 +658,7 @@ class EventClient {
|
|
|
620
658
|
throw new InvalidParameter('You must specify both event spec and callback function');
|
|
621
659
|
}
|
|
622
660
|
|
|
623
|
-
if (!
|
|
661
|
+
if (!Object.hasOwn(eventSpecification, 'event_name')) {
|
|
624
662
|
throw new InvalidParameter('Your event specification must contain event name');
|
|
625
663
|
}
|
|
626
664
|
|
|
@@ -1504,7 +1542,7 @@ function camelcaseKeys(input, options) {
|
|
|
1504
1542
|
|
|
1505
1543
|
const options = {
|
|
1506
1544
|
wssServer: 'wss://api.telegenta.com/sip/',
|
|
1507
|
-
eventServer: 'wss://
|
|
1545
|
+
eventServer: 'wss://esvc.telegenta.com/',
|
|
1508
1546
|
provisionToken: null,
|
|
1509
1547
|
authorizationUsername: null,
|
|
1510
1548
|
password: null,
|
|
@@ -1625,10 +1663,10 @@ class Phone extends EventEmitter {
|
|
|
1625
1663
|
this._oscillator2 = null;
|
|
1626
1664
|
|
|
1627
1665
|
// Set default logger
|
|
1628
|
-
this.log = logger ? logger : new LogHandler(LOGLVL
|
|
1666
|
+
this.log = logger ? logger : new LogHandler(LOGLVL.INFO);
|
|
1629
1667
|
|
|
1630
1668
|
// Handle jssip debug logs
|
|
1631
|
-
if (this.log.level === LOGLVL
|
|
1669
|
+
if (this.log.level === LOGLVL.DEBUG) {
|
|
1632
1670
|
JsSIP.debug.disable('JsSIP:*');
|
|
1633
1671
|
} else {
|
|
1634
1672
|
JsSIP.debug.disable('JsSIP:*');
|
|
@@ -1703,10 +1741,10 @@ class Phone extends EventEmitter {
|
|
|
1703
1741
|
|
|
1704
1742
|
// Unpack provisioning token if specified
|
|
1705
1743
|
if (this._options.provisionToken) {
|
|
1706
|
-
let pt
|
|
1744
|
+
let pt;
|
|
1707
1745
|
try {
|
|
1708
1746
|
pt = JSON.parse(atob(this._options.provisionToken));
|
|
1709
|
-
} catch
|
|
1747
|
+
} catch {
|
|
1710
1748
|
throw new InvalidOptions('provisionToken', 'Invalid token');
|
|
1711
1749
|
}
|
|
1712
1750
|
|
|
@@ -1782,7 +1820,7 @@ class Phone extends EventEmitter {
|
|
|
1782
1820
|
);
|
|
1783
1821
|
}
|
|
1784
1822
|
|
|
1785
|
-
let wsSocket
|
|
1823
|
+
let wsSocket;
|
|
1786
1824
|
try {
|
|
1787
1825
|
wsSocket = new JsSIP.WebSocketInterface(this._options.wssServer);
|
|
1788
1826
|
} catch (err) {
|
|
@@ -1894,7 +1932,7 @@ class Phone extends EventEmitter {
|
|
|
1894
1932
|
try {
|
|
1895
1933
|
this._eventClient.disconnect();
|
|
1896
1934
|
delete this._eventClient;
|
|
1897
|
-
} catch
|
|
1935
|
+
} catch {
|
|
1898
1936
|
this.log.warning('Unable to disconnect event client');
|
|
1899
1937
|
}
|
|
1900
1938
|
|
|
@@ -1913,9 +1951,11 @@ class Phone extends EventEmitter {
|
|
|
1913
1951
|
* @param {number} [params.answerCallTimeout=0] Hangup call with NO ANSWER if call is not picked up before specified interval (seconds). If 0, timeout is disabled.
|
|
1914
1952
|
* @param {number} [params.maximumCallCost] Maximum call cost (in customer currency) allowed for call. The call will automatically be stopped when the specified cost has been reached. Setting this to 0 disables checking for maximum cost.
|
|
1915
1953
|
* @param {string} [params.metaInfo] Add meta info to call, which will be available in server events and inside call complete webhook (max 256 chars)
|
|
1954
|
+
* @param {object} [params.setHeader] Custom SIP headers to add to the INVITE, as {name: value}.
|
|
1916
1955
|
* @return {Session} object for call
|
|
1917
1956
|
* @throws NotReady - If phone is not ready to call
|
|
1918
1957
|
* @throws NotAllowed - If there are calls currently connecting
|
|
1958
|
+
* @throws InvalidParameter - If setHeader contains an invalid header name or container
|
|
1919
1959
|
*
|
|
1920
1960
|
*/
|
|
1921
1961
|
call(number, params = {}) {
|
|
@@ -1940,6 +1980,7 @@ class Phone extends EventEmitter {
|
|
|
1940
1980
|
explicitCallerId: null,
|
|
1941
1981
|
explicitShortCallerId: null,
|
|
1942
1982
|
metaInfo: null,
|
|
1983
|
+
setHeader: null,
|
|
1943
1984
|
_isListenCall: false,
|
|
1944
1985
|
},
|
|
1945
1986
|
params,
|
|
@@ -1982,7 +2023,7 @@ class Phone extends EventEmitter {
|
|
|
1982
2023
|
*/
|
|
1983
2024
|
getSessionByUUID(uuid) {
|
|
1984
2025
|
for (let key in this._activeSessions) {
|
|
1985
|
-
if (this._activeSessions
|
|
2026
|
+
if (Object.hasOwn(this._activeSessions, key)) {
|
|
1986
2027
|
if (this._activeSessions[key].uuid === uuid) {
|
|
1987
2028
|
return this._activeSessions[key];
|
|
1988
2029
|
}
|
|
@@ -2007,7 +2048,7 @@ class Phone extends EventEmitter {
|
|
|
2007
2048
|
get sessions() {
|
|
2008
2049
|
let s = [];
|
|
2009
2050
|
for (let key in this._activeSessions) {
|
|
2010
|
-
if (this._activeSessions
|
|
2051
|
+
if (Object.hasOwn(this._activeSessions, key)) {
|
|
2011
2052
|
s.push(this._activeSessions[key]);
|
|
2012
2053
|
}
|
|
2013
2054
|
}
|
|
@@ -2172,7 +2213,7 @@ class Phone extends EventEmitter {
|
|
|
2172
2213
|
|
|
2173
2214
|
// Iterate through all active calls and hold them
|
|
2174
2215
|
for (let key in this._activeSessions) {
|
|
2175
|
-
if (this._activeSessions
|
|
2216
|
+
if (Object.hasOwn(this._activeSessions, key)) {
|
|
2176
2217
|
let session = this._activeSessions[key];
|
|
2177
2218
|
if (session.state === 'ACTIVE') {
|
|
2178
2219
|
session.hold();
|
|
@@ -2246,7 +2287,7 @@ class Phone extends EventEmitter {
|
|
|
2246
2287
|
// Return true if there are calls currently connecting
|
|
2247
2288
|
_checkForConnectingCalls() {
|
|
2248
2289
|
for (let key in this._activeSessions) {
|
|
2249
|
-
if (this._activeSessions
|
|
2290
|
+
if (Object.hasOwn(this._activeSessions, key)) {
|
|
2250
2291
|
let session = this._activeSessions[key];
|
|
2251
2292
|
if (
|
|
2252
2293
|
session.state === 'INITIALIZING' ||
|
|
@@ -2495,6 +2536,23 @@ class Session extends EventEmitter {
|
|
|
2495
2536
|
}
|
|
2496
2537
|
}
|
|
2497
2538
|
|
|
2539
|
+
// Custom headers last, so they cannot displace the built-in ones.
|
|
2540
|
+
// Must be before _UA.call() below: jssip clones extraHeaders synchronously
|
|
2541
|
+
// inside connect(), so anything pushed after that call never reaches the INVITE.
|
|
2542
|
+
let customHeaders;
|
|
2543
|
+
try {
|
|
2544
|
+
customHeaders = buildCustomHeaders(callParams.setHeader);
|
|
2545
|
+
} catch (error) {
|
|
2546
|
+
if (error instanceof TypeError) {
|
|
2547
|
+
throw new InvalidParameter(error.message);
|
|
2548
|
+
}
|
|
2549
|
+
throw error;
|
|
2550
|
+
}
|
|
2551
|
+
for (const header of customHeaders) {
|
|
2552
|
+
this.log.debug('Adding custom header: ' + header);
|
|
2553
|
+
options.extraHeaders.push(header);
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2498
2556
|
// Prepare invite uri
|
|
2499
2557
|
const uri = 'sip:' + number + '@telegenta.com';
|
|
2500
2558
|
|
|
@@ -2613,7 +2671,7 @@ class Session extends EventEmitter {
|
|
|
2613
2671
|
self.log.info(`Answer call timeout after ${self._answerCallTimeout} seconds`);
|
|
2614
2672
|
try {
|
|
2615
2673
|
self.hangup();
|
|
2616
|
-
} catch
|
|
2674
|
+
} catch {
|
|
2617
2675
|
self.log.info('No active session, ignoring answer call timeout');
|
|
2618
2676
|
}
|
|
2619
2677
|
}, self._answerCallTimeout * 1000);
|
|
@@ -2867,6 +2925,30 @@ class Session extends EventEmitter {
|
|
|
2867
2925
|
}
|
|
2868
2926
|
}
|
|
2869
2927
|
|
|
2928
|
+
/**
|
|
2929
|
+
* Raw WebRTC statistics for this session's peer connection.
|
|
2930
|
+
*
|
|
2931
|
+
* Returns the browser's own `RTCStatsReport` untouched, so callers can read
|
|
2932
|
+
* any statistic the browser exposes - RTP packet and byte counters, the
|
|
2933
|
+
* DTLS/ICE transport state, codec and candidate-pair details.
|
|
2934
|
+
*
|
|
2935
|
+
* @returns {Promise<RTCStatsReport>} The peer connection's statistics
|
|
2936
|
+
* @throws NoActiveSession - If the session has no established peer connection
|
|
2937
|
+
* (thrown synchronously, before the promise is created)
|
|
2938
|
+
*
|
|
2939
|
+
* @example
|
|
2940
|
+
* const report = await session.getRTCStats();
|
|
2941
|
+
* for (const stat of report.values()) {
|
|
2942
|
+
* if (stat.type === 'inbound-rtp') console.log(stat.packetsReceived);
|
|
2943
|
+
* }
|
|
2944
|
+
*/
|
|
2945
|
+
getRTCStats() {
|
|
2946
|
+
if (!this._webRTCSession || !this._webRTCSession.connection) {
|
|
2947
|
+
throw new NoActiveSession('no peer connection for this session');
|
|
2948
|
+
}
|
|
2949
|
+
return this._webRTCSession.connection.getStats();
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2870
2952
|
// RECORDING
|
|
2871
2953
|
/**
|
|
2872
2954
|
* Start recording
|
|
@@ -3660,4 +3742,4 @@ class Session extends EventEmitter {
|
|
|
3660
3742
|
}
|
|
3661
3743
|
}
|
|
3662
3744
|
|
|
3663
|
-
export { EventClient, EventServiceNotConnected, InvalidMediaDevice, InvalidOptions, InvalidParameter, LOGLVL
|
|
3745
|
+
export { EventClient, EventServiceNotConnected, InvalidMediaDevice, InvalidOptions, InvalidParameter, LOGLVL, LogHandler, MediaNotPrepared, NoActiveSession, NotAllowed, NotReady, Phone, Request, Requestor, Session, UnableToConnect, browserIsChrome, browserIsEdge, browserIsFirefox, browserIsOpera, browserIsSafari, buildCustomHeaders, compareObjects, createDurationString, createUUID };
|