@telegenta/webclient 3.0.1 → 3.1.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.
@@ -45,7 +45,7 @@
45
45
  eventClient = new Telegenta.EventClient(logger);
46
46
 
47
47
  // Connect to event server
48
- eventClient.connect('wss://api.telegenta.com/es/').then(function () {
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}`);
@@ -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://api.telegenta.com/es/';
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
@@ -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
+ '<': '&lt;',
301
+ '>': '&gt;',
302
+ '"': '&quot;',
303
+ "'": '&#39;',
304
+ '\r': '&#13;',
305
+ '\n': '&#10;',
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
 
@@ -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://api.telegenta.com/es/';
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();
@@ -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://api.telegenta.com/es/',
1545
+ eventServer: 'wss://esvc.telegenta.com/',
1508
1546
  provisionToken: null,
1509
1547
  authorizationUsername: null,
1510
1548
  password: null,
@@ -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,
@@ -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
 
@@ -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$1 as LOGLVL, LogHandler, MediaNotPrepared, NoActiveSession, NotAllowed, NotReady, Phone, Request, Requestor, Session, UnableToConnect, browserIsChrome, browserIsEdge, browserIsFirefox, browserIsOpera, browserIsSafari, compareObjects, createDurationString, createUUID };
3745
+ export { EventClient, EventServiceNotConnected, InvalidMediaDevice, InvalidOptions, InvalidParameter, LOGLVL$1 as LOGLVL, LogHandler, MediaNotPrepared, NoActiveSession, NotAllowed, NotReady, Phone, Request, Requestor, Session, UnableToConnect, browserIsChrome, browserIsEdge, browserIsFirefox, browserIsOpera, browserIsSafari, buildCustomHeaders, compareObjects, createDurationString, createUUID };