@telegenta/webclient 3.0.0 → 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.
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
  }
@@ -197,6 +198,16 @@ class NotAllowed extends Error {
197
198
  }
198
199
  }
199
200
 
201
+ /**
202
+ * Exception raised if event service is not connected
203
+ */
204
+ class EventServiceNotConnected extends Error {
205
+ constructor() {
206
+ super();
207
+ this.name = 'EventServiceNotConnected';
208
+ }
209
+ }
210
+
200
211
  /*
201
212
  * TOOLBOX
202
213
  */
@@ -281,6 +292,43 @@ function browserIsEdge() {
281
292
  return (navigator.userAgent.indexOf("Edge") !== -1)
282
293
  }
283
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
+
284
332
  // Request timeout after 10 sec
285
333
  const REQUEST_TIMEOUT = 10000;
286
334
 
@@ -444,7 +492,7 @@ class Requestor extends EventEmitter {
444
492
  resolve: resolve,
445
493
  reject: reject,
446
494
  timeoutObj: setTimeout(function () {
447
- self.log.warning(`Timeout on request ${r.id}, rejecting!`);
495
+ self.log.info(`Timeout on request ${r.id}, rejecting!`);
448
496
  self.removePendingRequest(r.id);
449
497
  reject("Request timed out");
450
498
  }, REQUEST_TIMEOUT),
@@ -512,7 +560,7 @@ class Requestor extends EventEmitter {
512
560
  // Keep alive in seconds
513
561
  const PING_INTERVAL = 50;
514
562
 
515
- const ES_DEFAULT_SERVER_URL = "wss://api.telegenta.com/es/";
563
+ const ES_DEFAULT_SERVER_URL = 'wss://esvc.telegenta.com/';
516
564
 
517
565
  // Starting retry interval
518
566
  const RETRY_INTERVAL_START = 1 + Math.random();
@@ -550,7 +598,7 @@ class EventClient {
550
598
  this.log = logger ? logger : new LogHandler(LOGLVL.ERROR);
551
599
  this._requestor = new Requestor(this.log);
552
600
 
553
- this._requestor.on("event", (request) => {
601
+ this._requestor.on('event', (request) => {
554
602
  this._handleIncomingEvent(request);
555
603
  });
556
604
  }
@@ -573,11 +621,11 @@ class EventClient {
573
621
 
574
622
  /**
575
623
  * Disconnect from event service and clear all active subscriptions
576
- * @throws NoActiveSession if not connected
624
+ * @throws EventServiceNotConnected if not connected
577
625
  */
578
626
  disconnect() {
579
627
  if (this._socket.readyState !== 1) {
580
- throw new NoActiveSession();
628
+ throw new EventServiceNotConnected();
581
629
  }
582
630
 
583
631
  this._activeSubscriptions = [];
@@ -602,26 +650,29 @@ class EventClient {
602
650
  * @param eventSpecification object Event parameters (please check docs for further description)
603
651
  * @param callback object Callback function (will be called when event is received)
604
652
  * @throws InvalidParameter
653
+ * @throws EventServiceNotConnected
605
654
  * @returns object Promise on subscription
606
655
  */
607
656
  subscribe(eventSpecification, callback) {
608
657
  if (!eventSpecification || !callback) {
609
- throw new InvalidParameter("You must specify both event spec and callback function");
658
+ throw new InvalidParameter('You must specify both event spec and callback function');
610
659
  }
611
660
 
612
- if (!eventSpecification.hasOwnProperty("event_name")) {
613
- throw new InvalidParameter("Your event specification must contain event name");
661
+ if (!eventSpecification.hasOwnProperty('event_name')) {
662
+ throw new InvalidParameter('Your event specification must contain event name');
614
663
  }
615
664
 
616
665
  if (!this.isConnected()) {
617
- throw new NoActiveSession();
666
+ throw new EventServiceNotConnected();
618
667
  }
619
668
 
620
669
  const self = this;
621
670
  return new Promise(function (resolve, reject) {
622
- self._requestor.sendRequest(self._socket, "subscribe", eventSpecification).then(
671
+ self._requestor.sendRequest(self._socket, 'subscribe', eventSpecification).then(
623
672
  function (subscriptionId) {
624
- self.log.debug(`Subscription to '${eventSpecification.event_name}' was successful, ID = ${subscriptionId}`);
673
+ self.log.debug(
674
+ `Subscription to '${eventSpecification.event_name}' was successful, ID = ${subscriptionId}`,
675
+ );
625
676
  self._activeSubscriptions.push(new EventSubscription(subscriptionId, eventSpecification, callback));
626
677
  resolve(subscriptionId);
627
678
  },
@@ -637,22 +688,23 @@ class EventClient {
637
688
  * Unsubscribe event messages
638
689
  * @param subscriptionId Object Event specification to unsubscribe (must match specification used on subscribe)
639
690
  * @throws InvalidParameter
691
+ * @throws EventServiceNotConnected
640
692
  * @returns object Promise on unsubscribe
641
693
  */
642
694
  unsubscribe(subscriptionId) {
643
695
  if (!subscriptionId) {
644
- throw new InvalidParameter("You must specify subscription id");
696
+ throw new InvalidParameter('You must specify subscription id');
645
697
  }
646
698
 
647
699
  if (!this.isConnected()) {
648
- throw new NoActiveSession();
700
+ throw new EventServiceNotConnected();
649
701
  }
650
702
 
651
- this.log.info("Unsubscribe id: " + subscriptionId);
703
+ this.log.info('Unsubscribe id: ' + subscriptionId);
652
704
 
653
705
  const self = this;
654
706
  return new Promise(function (resolve, reject) {
655
- self._requestor.sendRequest(self._socket, "unsubscribe", { id: subscriptionId }).then(
707
+ self._requestor.sendRequest(self._socket, 'unsubscribe', { id: subscriptionId }).then(
656
708
  (data) => {
657
709
  self.log.info(`Unsubscribe to '${subscriptionId}' was successful`);
658
710
  self._removeSubscription(subscriptionId);
@@ -670,12 +722,12 @@ class EventClient {
670
722
  * Fire event message
671
723
  * @param type Event type to fire
672
724
  * @param payload Event payload
673
- * @throws NoActiveSession
725
+ * @throws EventServiceNotConnected
674
726
  * @returns object Promise on event
675
727
  */
676
728
  fireEvent(type, payload) {
677
729
  if (!this.isConnected()) {
678
- throw new NoActiveSession();
730
+ throw new EventServiceNotConnected();
679
731
  }
680
732
 
681
733
  return new Promise((resolve, reject) => {
@@ -708,20 +760,20 @@ class EventClient {
708
760
 
709
761
  // Connect to event service
710
762
  _connectServer() {
711
- this.log.info("Connecting to event server: " + this._serverAddress);
763
+ this.log.info('Connecting to event server: ' + this._serverAddress);
712
764
 
713
765
  const self = this;
714
766
  return new Promise(function (resolve, reject) {
715
767
  if (self._socket) {
716
768
  switch (self._socket.readyState) {
717
769
  case 0:
718
- reject("Unable to connect, already connecting");
770
+ reject('Unable to connect, already connecting');
719
771
  return;
720
772
  case 1:
721
- reject("Unable to connect, already connected");
773
+ reject('Unable to connect, already connected');
722
774
  return;
723
775
  case 2:
724
- reject("Unable to connect, socket is still closing");
776
+ reject('Unable to connect, socket is still closing');
725
777
  return;
726
778
  }
727
779
  }
@@ -730,8 +782,8 @@ class EventClient {
730
782
  let was_connected = false;
731
783
 
732
784
  // CONNECTION OPEN
733
- self._socket.addEventListener("open", function (/*event*/) {
734
- self.log.info("Successfully connected to event service");
785
+ self._socket.addEventListener('open', function (/*event*/) {
786
+ self.log.info('Successfully connected to event service');
735
787
 
736
788
  was_connected = true;
737
789
  // After successful connect we enable auto connect and reset reconnect timer
@@ -739,7 +791,7 @@ class EventClient {
739
791
  self._reconnectRetrySeconds = RETRY_INTERVAL_START;
740
792
 
741
793
  // Hook up event for incoming messages to requestor
742
- self._socket.addEventListener("message", function (event) {
794
+ self._socket.addEventListener('message', function (event) {
743
795
  self._requestor.receiveMessage(self._socket, event.data);
744
796
  });
745
797
 
@@ -751,7 +803,7 @@ class EventClient {
751
803
  // self.log.debug('Pong');
752
804
  },
753
805
  function (reason) {
754
- self.log.warning("Ping failed: " + reason);
806
+ self.log.warning('Ping failed: ' + reason);
755
807
  self._reconnectServer();
756
808
  },
757
809
  );
@@ -772,19 +824,19 @@ class EventClient {
772
824
  });
773
825
 
774
826
  // CONNECTION CLOSE
775
- self._socket.addEventListener("close", function (/*event*/) {
776
- self.log.info("Disconnected from event service");
827
+ self._socket.addEventListener('close', function (/*event*/) {
828
+ self.log.info('Disconnected from event service');
777
829
  clearTimeout(self._pingTimerObj);
778
830
  // We try to reconnect if possible
779
831
  self._reconnectServer();
780
832
  });
781
833
 
782
834
  // CONNECTION ERROR
783
- self._socket.addEventListener("error", function (error) {
784
- self.log.error("Websocket connection error");
835
+ self._socket.addEventListener('error', function (error) {
836
+ self.log.error('Websocket connection error');
785
837
  // Only reject promise if no connection was made (in that case promise is already resolved)
786
838
  if (!was_connected) {
787
- reject("Unable to connect: " + error);
839
+ reject('Unable to connect: ' + error);
788
840
  }
789
841
  });
790
842
  });
@@ -798,10 +850,10 @@ class EventClient {
798
850
  setTimeout(function () {
799
851
  self._connectServer().then(
800
852
  function () {
801
- self.log.info("Reconnect attempt successful");
853
+ self.log.info('Reconnect attempt successful');
802
854
  },
803
855
  function () {
804
- self.log.warning("Reconnect attempt failed");
856
+ self.log.warning('Reconnect attempt failed');
805
857
  },
806
858
  );
807
859
  if (self._reconnectRetrySeconds <= RETRY_MAX_BACKOFF) {
@@ -809,7 +861,7 @@ class EventClient {
809
861
  }
810
862
  }, self._reconnectRetrySeconds * 1000);
811
863
  } else {
812
- this.log.info("Auto reconnect disabled (disconnecting?)");
864
+ this.log.info('Auto reconnect disabled (disconnecting?)');
813
865
  }
814
866
  }
815
867
 
@@ -818,7 +870,9 @@ class EventClient {
818
870
  const subId = request.data.subscription_id;
819
871
  for (let sub of this._activeSubscriptions) {
820
872
  if (sub.id === subId) {
821
- this.log.debug(`Received event for subcription id ${sub.id} payload: ` + JSON.stringify(request.data.payload));
873
+ this.log.debug(
874
+ `Received event for subcription id ${sub.id} payload: ` + JSON.stringify(request.data.payload),
875
+ );
822
876
  try {
823
877
  sub.callback(request.data.payload);
824
878
  } catch (e) {
@@ -1487,8 +1541,8 @@ function camelcaseKeys(input, options) {
1487
1541
  // 'use strict';
1488
1542
 
1489
1543
  const options = {
1490
- wssServer: "wss://api.telegenta.com/sip/",
1491
- eventServer: "wss://api.telegenta.com/es/",
1544
+ wssServer: 'wss://api.telegenta.com/sip/',
1545
+ eventServer: 'wss://esvc.telegenta.com/',
1492
1546
  provisionToken: null,
1493
1547
  authorizationUsername: null,
1494
1548
  password: null,
@@ -1521,9 +1575,9 @@ const DTMF_FREQUENCY_TABLE = {
1521
1575
  8: [1336, 852],
1522
1576
  9: [1477, 852],
1523
1577
  c: [1633, 852],
1524
- "*": [1209, 941],
1578
+ '*': [1209, 941],
1525
1579
  0: [1336, 941],
1526
- "#": [1477, 941],
1580
+ '#': [1477, 941],
1527
1581
  d: [1633, 941],
1528
1582
  };
1529
1583
 
@@ -1594,11 +1648,11 @@ class Phone extends EventEmitter {
1594
1648
  this._activeSessions = {};
1595
1649
 
1596
1650
  // Create remote audio element
1597
- this._remoteAudioElement = document.createElement("AUDIO");
1651
+ this._remoteAudioElement = document.createElement('AUDIO');
1598
1652
  this._remoteAudioElement.autoplay = true;
1599
1653
 
1600
1654
  // Tone generator
1601
- this._toneAudioElement = document.createElement("AUDIO");
1655
+ this._toneAudioElement = document.createElement('AUDIO');
1602
1656
  this._toneAudioElement.autoplay = true;
1603
1657
  this._toneTimer = null;
1604
1658
  this._repeatToneActive = false;
@@ -1613,9 +1667,9 @@ class Phone extends EventEmitter {
1613
1667
 
1614
1668
  // Handle jssip debug logs
1615
1669
  if (this.log.level === LOGLVL$1.DEBUG) {
1616
- JsSIP.debug.disable("JsSIP:*");
1670
+ JsSIP.debug.disable('JsSIP:*');
1617
1671
  } else {
1618
- JsSIP.debug.disable("JsSIP:*");
1672
+ JsSIP.debug.disable('JsSIP:*');
1619
1673
  }
1620
1674
  }
1621
1675
 
@@ -1627,7 +1681,7 @@ class Phone extends EventEmitter {
1627
1681
  *
1628
1682
  */
1629
1683
  prepareMedia() {
1630
- this.log.info("Get media permission");
1684
+ this.log.info('Get media permission');
1631
1685
  const self = this;
1632
1686
  const constraints = { audio: true, video: false };
1633
1687
  return new Promise(function (resolve, reject) {
@@ -1635,13 +1689,13 @@ class Phone extends EventEmitter {
1635
1689
  () => {
1636
1690
  self._mediaPermission = true;
1637
1691
  self._mediaPermissionError = false;
1638
- self.log.info("Media permission granted");
1692
+ self.log.info('Media permission granted');
1639
1693
  resolve();
1640
1694
  },
1641
1695
  (err) => {
1642
1696
  self._mediaPermission = false;
1643
1697
  self._mediaPermissionError = true;
1644
- self.log.error("Denied media permission: " + err);
1698
+ self.log.error('Denied media permission: ' + err);
1645
1699
  reject(err);
1646
1700
  },
1647
1701
  );
@@ -1676,7 +1730,7 @@ class Phone extends EventEmitter {
1676
1730
  this._checkMediaPermission();
1677
1731
  // Check phone is not running already, if so stop it first
1678
1732
  if (this._isStarted) {
1679
- this.log.warning("Phone already started, stopping first");
1733
+ this.log.warning('Phone already started, stopping first');
1680
1734
  this.stop();
1681
1735
  }
1682
1736
 
@@ -1691,29 +1745,29 @@ class Phone extends EventEmitter {
1691
1745
  try {
1692
1746
  pt = JSON.parse(atob(this._options.provisionToken));
1693
1747
  } catch (err) {
1694
- throw new InvalidOptions("provisionToken", "Invalid token");
1748
+ throw new InvalidOptions('provisionToken', 'Invalid token');
1695
1749
  }
1696
1750
 
1697
- this.log.info("Using provisioning token authorization");
1751
+ this.log.info('Using provisioning token authorization');
1698
1752
  this._options.wssServer = pt.wss_server;
1699
1753
  this._options.eventServer = pt.event_server;
1700
1754
  this._options.authorizationUsername = pt.wss_username;
1701
1755
  this._options.password = pt.wss_password;
1702
1756
  this._options.callToken = pt.wss_token;
1703
1757
  } else {
1704
- this.log.info("Using manual specified credentials");
1758
+ this.log.info('Using manual specified credentials');
1705
1759
 
1706
1760
  // Check auth info is provided
1707
1761
  if (!this._options.authorizationUsername) {
1708
- throw new InvalidOptions("authorizationUsername", "Missing username");
1762
+ throw new InvalidOptions('authorizationUsername', 'Missing username');
1709
1763
  }
1710
1764
 
1711
1765
  if (!this._options.password) {
1712
- throw new InvalidOptions("password", "Missing password");
1766
+ throw new InvalidOptions('password', 'Missing password');
1713
1767
  }
1714
1768
 
1715
1769
  if (!this._options.callToken) {
1716
- throw new InvalidOptions("callToken", "Missing call token");
1770
+ throw new InvalidOptions('callToken', 'Missing call token');
1717
1771
  }
1718
1772
  }
1719
1773
 
@@ -1736,18 +1790,18 @@ class Phone extends EventEmitter {
1736
1790
  if (this._options.useCallDataInfoEvent) {
1737
1791
  // Subscribe to calldata_available events
1738
1792
  let eventSpec = {
1739
- event_name: "calldata_available",
1793
+ event_name: 'calldata_available',
1740
1794
  call_token: this._options.callToken,
1741
1795
  };
1742
1796
 
1743
1797
  this._eventClient
1744
1798
  .subscribe(eventSpec, (data) => {
1745
1799
  try {
1746
- this.log.debug("Received remote call data info");
1800
+ this.log.debug('Received remote call data info');
1747
1801
  this.log.debug(data);
1748
1802
  this._fireCallDataInfoEvent(data);
1749
1803
  } catch (e) {
1750
- this.log.error("Event callback failed: " + e);
1804
+ this.log.error('Event callback failed: ' + e);
1751
1805
  }
1752
1806
  })
1753
1807
  .then(
@@ -1770,10 +1824,10 @@ class Phone extends EventEmitter {
1770
1824
  try {
1771
1825
  wsSocket = new JsSIP.WebSocketInterface(this._options.wssServer);
1772
1826
  } catch (err) {
1773
- throw new UnableToConnect("Unable to connect phone websocket: " + err);
1827
+ throw new UnableToConnect('Unable to connect phone websocket: ' + err);
1774
1828
  }
1775
1829
 
1776
- this.log.info("Starting phone");
1830
+ this.log.info('Starting phone');
1777
1831
  this.log.debug(this._options);
1778
1832
 
1779
1833
  this._UA = new JsSIP.UA({
@@ -1782,15 +1836,15 @@ class Phone extends EventEmitter {
1782
1836
  password: this._options.password,
1783
1837
  register: this._options.register,
1784
1838
  display_name: this._options.authorizationUsername,
1785
- uri: this._options.authorizationUsername + "@telegenta.com",
1839
+ uri: this._options.authorizationUsername + '@telegenta.com',
1786
1840
  session_timers: false,
1787
1841
  });
1788
1842
 
1789
1843
  // Setup remote media handler
1790
1844
  const self = this;
1791
- this._UA.on("newRTCSession", function (data) {
1792
- self.log.debug("New RTCSession");
1793
- if (data.originator === "remote") {
1845
+ this._UA.on('newRTCSession', function (data) {
1846
+ self.log.debug('New RTCSession');
1847
+ if (data.originator === 'remote') {
1794
1848
  // Create new session
1795
1849
  const session = new Session(self);
1796
1850
 
@@ -1803,18 +1857,20 @@ class Phone extends EventEmitter {
1803
1857
  // Check if we have other sessions and reject call if autoBusyIncoming is true
1804
1858
  if (self._options.autoBusyIncoming) {
1805
1859
  if (Object.keys(self._activeSessions).length > 0) {
1806
- self.log.info(`Autorejecting incoming call ${session.uuid} because there is already active calls`);
1807
- session.reject(486, "Busy");
1860
+ self.log.info(
1861
+ `Autorejecting incoming call ${session.uuid} because there is already active calls`,
1862
+ );
1863
+ session.reject(486, 'Busy');
1808
1864
  return;
1809
1865
  } else {
1810
- self.log.debug("There are no other sessions, adding incoming call");
1866
+ self.log.debug('There are no other sessions, adding incoming call');
1811
1867
  }
1812
1868
  }
1813
1869
 
1814
1870
  self._addSession(session);
1815
1871
 
1816
1872
  // Set new session state
1817
- session._setSessionState("INCOMING");
1873
+ session._setSessionState('INCOMING');
1818
1874
 
1819
1875
  // Now fire incomingCall event
1820
1876
  self._fireIncomingCallEvent(session);
@@ -1822,33 +1878,33 @@ class Phone extends EventEmitter {
1822
1878
  });
1823
1879
 
1824
1880
  // Setup connection state event subscriptions
1825
- this._UA.on("connecting", function (/* data */) {
1826
- self._setConnectionState("CONNECTING");
1881
+ this._UA.on('connecting', function (/* data */) {
1882
+ self._setConnectionState('CONNECTING');
1827
1883
  });
1828
1884
 
1829
- this._UA.on("connected", function (/* data */) {
1830
- self._setConnectionState("CONNECTED");
1885
+ this._UA.on('connected', function (/* data */) {
1886
+ self._setConnectionState('CONNECTED');
1831
1887
  });
1832
1888
 
1833
- this._UA.on("disconnected", function (/* data */) {
1834
- self._setConnectionState("DISCONNECTED");
1889
+ this._UA.on('disconnected', function (/* data */) {
1890
+ self._setConnectionState('DISCONNECTED');
1835
1891
  });
1836
1892
 
1837
- this._UA.on("registered", function (/* data */) {
1838
- self._setConnectionState("REGISTERED");
1893
+ this._UA.on('registered', function (/* data */) {
1894
+ self._setConnectionState('REGISTERED');
1839
1895
  });
1840
1896
 
1841
- this._UA.on("unregistered", function (/* data */) {
1842
- self._setConnectionState("UNREGISTERED");
1897
+ this._UA.on('unregistered', function (/* data */) {
1898
+ self._setConnectionState('UNREGISTERED');
1843
1899
  });
1844
1900
 
1845
- this._UA.on("registrationFailed", function (/* data */) {
1846
- self._setConnectionState("REGISTRATION_FAILED");
1901
+ this._UA.on('registrationFailed', function (/* data */) {
1902
+ self._setConnectionState('REGISTRATION_FAILED');
1847
1903
  });
1848
1904
 
1849
1905
  this._UA.start();
1850
1906
  this._isStarted = true;
1851
- this.log.info("Successfully started phone");
1907
+ this.log.info('Successfully started phone');
1852
1908
  }
1853
1909
 
1854
1910
  /**
@@ -1858,7 +1914,7 @@ class Phone extends EventEmitter {
1858
1914
  stop() {
1859
1915
  // Check phone is started first
1860
1916
  if (!this._isStarted) {
1861
- throw new NotReady("Phone is not started");
1917
+ throw new NotReady('Phone is not started');
1862
1918
  }
1863
1919
 
1864
1920
  // Make sure events are not firing anymore, we can't rely on GC.
@@ -1870,18 +1926,18 @@ class Phone extends EventEmitter {
1870
1926
 
1871
1927
  // Disconnect event client if needed
1872
1928
  if (!this._eventClient) {
1873
- this.log.debug("Event client not running, not stopping");
1929
+ this.log.debug('Event client not running, not stopping');
1874
1930
  return;
1875
1931
  }
1876
1932
  try {
1877
1933
  this._eventClient.disconnect();
1878
1934
  delete this._eventClient;
1879
1935
  } catch (e) {
1880
- this.log.warning("Unable to disconnect event client");
1936
+ this.log.warning('Unable to disconnect event client');
1881
1937
  }
1882
1938
 
1883
1939
  this._isStarted = false;
1884
- this.log.info("Phone client successfully stopped");
1940
+ this.log.info('Phone client successfully stopped');
1885
1941
  }
1886
1942
 
1887
1943
  /**
@@ -1895,20 +1951,22 @@ class Phone extends EventEmitter {
1895
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.
1896
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.
1897
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}.
1898
1955
  * @return {Session} object for call
1899
1956
  * @throws NotReady - If phone is not ready to call
1900
1957
  * @throws NotAllowed - If there are calls currently connecting
1958
+ * @throws InvalidParameter - If setHeader contains an invalid header name or container
1901
1959
  *
1902
1960
  */
1903
1961
  call(number, params = {}) {
1904
1962
  // Check phone is started first
1905
1963
  if (!this._isStarted) {
1906
- throw new NotReady("Phone is not started");
1964
+ throw new NotReady('Phone is not started');
1907
1965
  }
1908
1966
 
1909
1967
  // Check we dont have any other connecting calls
1910
1968
  if (this._checkForConnectingCalls()) {
1911
- throw NotAllowed("Unable to make new calls while others are connecting");
1969
+ throw new NotAllowed('Unable to make new calls while others are connecting');
1912
1970
  }
1913
1971
 
1914
1972
  // Hold other calls if needed
@@ -1922,15 +1980,16 @@ class Phone extends EventEmitter {
1922
1980
  explicitCallerId: null,
1923
1981
  explicitShortCallerId: null,
1924
1982
  metaInfo: null,
1983
+ setHeader: null,
1925
1984
  _isListenCall: false,
1926
1985
  },
1927
1986
  params,
1928
1987
  );
1929
1988
 
1930
1989
  // Trim phone number to remove spaces and '()'
1931
- number = number.trim().replace(/ |\(|\)/g, "");
1990
+ number = number.trim().replace(/ |\(|\)/g, '');
1932
1991
 
1933
- this.log.info("Calling number: " + number);
1992
+ this.log.info('Calling number: ' + number);
1934
1993
 
1935
1994
  let session = new Session(this);
1936
1995
  session.newOutboundSession(number, callParams);
@@ -1946,12 +2005,12 @@ class Phone extends EventEmitter {
1946
2005
  * @throws NotAllowed - If there are calls currently connecting
1947
2006
  */
1948
2007
  listen(listenKey) {
1949
- const s = listenKey.split("@");
2008
+ const s = listenKey.split('@');
1950
2009
  let callUUID = s[0];
1951
- let mediaSwitch = "sip:" + s[1];
2010
+ let mediaSwitch = 'sip:' + s[1];
1952
2011
 
1953
- this.log.debug("Initiating listen to call with uuid " + callUUID + " on media switch " + mediaSwitch);
1954
- return this.call("listen-" + callUUID, {
2012
+ this.log.debug('Initiating listen to call with uuid ' + callUUID + ' on media switch ' + mediaSwitch);
2013
+ return this.call('listen-' + callUUID, {
1955
2014
  mediaSwitchURI: mediaSwitch,
1956
2015
  _isListenCall: true,
1957
2016
  });
@@ -2077,7 +2136,7 @@ class Phone extends EventEmitter {
2077
2136
 
2078
2137
  // Begin repeating tones
2079
2138
  _startRepeatTone(sequences) {
2080
- this.log.info("Repeating tone sequence started");
2139
+ this.log.info('Repeating tone sequence started');
2081
2140
  this.log.debug(sequences);
2082
2141
  this._repeatToneSequence = sequences;
2083
2142
  this._repeatToneSequencePos = 0;
@@ -2107,7 +2166,7 @@ class Phone extends EventEmitter {
2107
2166
  // End repeating tones
2108
2167
  _endRepeatTone() {
2109
2168
  if (this._repeatToneActive) {
2110
- this.log.info("Repeating tone stopped");
2169
+ this.log.info('Repeating tone stopped');
2111
2170
  this._repeatToneActive = false;
2112
2171
  this.stopTone();
2113
2172
  }
@@ -2118,7 +2177,7 @@ class Phone extends EventEmitter {
2118
2177
  // Create audio context
2119
2178
  const AudioContext = window.AudioContext || window.webkitAudioContext || false;
2120
2179
  if (!AudioContext) {
2121
- throw new NotAllowed("Audio API not supported by this browser");
2180
+ throw new NotAllowed('Audio API not supported by this browser');
2122
2181
  }
2123
2182
 
2124
2183
  const ctx = new AudioContext();
@@ -2146,17 +2205,17 @@ class Phone extends EventEmitter {
2146
2205
  * @throws NotAllowed If there are connection calls
2147
2206
  */
2148
2207
  holdAllCalls() {
2149
- this.log.info("Holding all active sessions");
2208
+ this.log.info('Holding all active sessions');
2150
2209
  // Check all session can be set on hold
2151
2210
  if (this._checkForConnectingCalls()) {
2152
- throw NotAllowed("A session is currently connecting");
2211
+ throw new NotAllowed('A session is currently connecting');
2153
2212
  }
2154
2213
 
2155
2214
  // Iterate through all active calls and hold them
2156
2215
  for (let key in this._activeSessions) {
2157
2216
  if (this._activeSessions.hasOwnProperty(key)) {
2158
2217
  let session = this._activeSessions[key];
2159
- if (session.state === "ACTIVE") {
2218
+ if (session.state === 'ACTIVE') {
2160
2219
  session.hold();
2161
2220
  }
2162
2221
  }
@@ -2185,9 +2244,9 @@ class Phone extends EventEmitter {
2185
2244
 
2186
2245
  // Fire event
2187
2246
  try {
2188
- this.emit("sessionCreated", { session: session });
2247
+ this.emit('sessionCreated', { session: session });
2189
2248
  } catch (e) {
2190
- this.log.error("Exception in sessionCreated event: " + e);
2249
+ this.log.error('Exception in sessionCreated event: ' + e);
2191
2250
  }
2192
2251
  }
2193
2252
 
@@ -2203,9 +2262,9 @@ class Phone extends EventEmitter {
2203
2262
 
2204
2263
  // Fire event
2205
2264
  try {
2206
- this.emit("sessionRemoved", { session: session });
2265
+ this.emit('sessionRemoved', { session: session });
2207
2266
  } catch (e) {
2208
- this.log.error("Exception in sessionRemoved event: " + e);
2267
+ this.log.error('Exception in sessionRemoved event: ' + e);
2209
2268
  }
2210
2269
 
2211
2270
  delete this._activeSessions[session.__containerId];
@@ -2230,7 +2289,12 @@ class Phone extends EventEmitter {
2230
2289
  for (let key in this._activeSessions) {
2231
2290
  if (this._activeSessions.hasOwnProperty(key)) {
2232
2291
  let session = this._activeSessions[key];
2233
- if (session.state === "INITIALIZING" || session.state === "CALLING" || session.state === "PROGRESS" || session.state === "INCOMING") {
2292
+ if (
2293
+ session.state === 'INITIALIZING' ||
2294
+ session.state === 'CALLING' ||
2295
+ session.state === 'PROGRESS' ||
2296
+ session.state === 'INCOMING'
2297
+ ) {
2234
2298
  return true;
2235
2299
  }
2236
2300
  }
@@ -2255,12 +2319,12 @@ class Phone extends EventEmitter {
2255
2319
  * @param {CONNECTION_STATE} state - The current connection state
2256
2320
  */
2257
2321
 
2258
- this.log.info("Connection state: " + state);
2322
+ this.log.info('Connection state: ' + state);
2259
2323
  this._connectionState = state;
2260
2324
  try {
2261
- this.emit("connectionStateChange", { state: state });
2325
+ this.emit('connectionStateChange', { state: state });
2262
2326
  } catch (e) {
2263
- this.log.error("Exception in connectionStateChange event: " + e);
2327
+ this.log.error('Exception in connectionStateChange event: ' + e);
2264
2328
  }
2265
2329
  }
2266
2330
 
@@ -2271,9 +2335,9 @@ class Phone extends EventEmitter {
2271
2335
  */
2272
2336
  _fireIncomingCallEvent(session) {
2273
2337
  try {
2274
- this.emit("incomingCall", session);
2338
+ this.emit('incomingCall', session);
2275
2339
  } catch (e) {
2276
- this.log.error("Exception in incomingCall event: " + e);
2340
+ this.log.error('Exception in incomingCall event: ' + e);
2277
2341
  }
2278
2342
  }
2279
2343
 
@@ -2326,12 +2390,12 @@ class Phone extends EventEmitter {
2326
2390
 
2327
2391
  // Add some extra convenience fields
2328
2392
  e.durationStr = createDurationString(e.duration);
2329
- e.answerDurationStr = e.answerDuration ? createDurationString(e.answerDuration) : "";
2330
- e.activeDurationStr = e.activeDuration ? createDurationString(e.activeDuration) : "";
2393
+ e.answerDurationStr = e.answerDuration ? createDurationString(e.answerDuration) : '';
2394
+ e.activeDurationStr = e.activeDuration ? createDurationString(e.activeDuration) : '';
2331
2395
  try {
2332
- this.emit("callDataInfo", e);
2396
+ this.emit('callDataInfo', e);
2333
2397
  } catch (e) {
2334
- this.log.error("Exception in callDataInfo event: " + e);
2398
+ this.log.error('Exception in callDataInfo event: ' + e);
2335
2399
  }
2336
2400
  }
2337
2401
 
@@ -2412,33 +2476,37 @@ class Session extends EventEmitter {
2412
2476
  let options = {
2413
2477
  mediaConstraints: { audio: true, video: false },
2414
2478
  pcConfig: {
2415
- iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }],
2479
+ iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }],
2416
2480
  },
2417
- extraHeaders: ["X-Call-token: " + this._phone.options.callToken, "X-P-UUID: " + this._callUUID, "X-Max-cost: " + callParams.maximumCallCost],
2481
+ extraHeaders: [
2482
+ 'X-Call-token: ' + this._phone.options.callToken,
2483
+ 'X-P-UUID: ' + this._callUUID,
2484
+ 'X-Max-cost: ' + callParams.maximumCallCost,
2485
+ ],
2418
2486
  };
2419
2487
 
2420
2488
  // Append explicit caller id if needed
2421
2489
  if (callParams.explicitCallerId) {
2422
- options.extraHeaders.push("X-CID-Number-Id: " + callParams.explicitCallerId);
2490
+ options.extraHeaders.push('X-CID-Number-Id: ' + callParams.explicitCallerId);
2423
2491
  }
2424
2492
 
2425
2493
  // Append explicit short cid if needed
2426
2494
  if (callParams.explicitShortCallerId) {
2427
- options.extraHeaders.push("X-Short-CID-Id: " + callParams.explicitShortCallerId);
2495
+ options.extraHeaders.push('X-Short-CID-Id: ' + callParams.explicitShortCallerId);
2428
2496
  }
2429
2497
 
2430
2498
  // Append meta info
2431
2499
  if (callParams.metaInfo) {
2432
- options.extraHeaders.push("X-Meta-Info: " + encodeURI(callParams.metaInfo.toString().substr(0, 255)));
2500
+ options.extraHeaders.push('X-Meta-Info: ' + encodeURI(callParams.metaInfo.toString().substr(0, 255)));
2433
2501
  }
2434
2502
 
2435
2503
  // Enable recording?
2436
2504
  if (callParams.record) {
2437
2505
  if (isNaN(callParams.record)) {
2438
- throw new InvalidParameter("Record parameter must be a number");
2506
+ throw new InvalidParameter('Record parameter must be a number');
2439
2507
  }
2440
2508
  this.log.debug(`Recording enabled and kept for ${callParams.record} days`);
2441
- options.extraHeaders.push("X-Record: " + callParams.record);
2509
+ options.extraHeaders.push('X-Record: ' + callParams.record);
2442
2510
  this._isRecording = true;
2443
2511
  } else {
2444
2512
  this._isRecording = false;
@@ -2449,8 +2517,8 @@ class Session extends EventEmitter {
2449
2517
 
2450
2518
  // Manually specified media switch
2451
2519
  if (callParams.mediaSwitchURI) {
2452
- this.log.debug("Requesting specific media switch " + callParams.mediaSwitchURI);
2453
- options.extraHeaders.push("X-RMS: " + callParams.mediaSwitchURI);
2520
+ this.log.debug('Requesting specific media switch ' + callParams.mediaSwitchURI);
2521
+ options.extraHeaders.push('X-RMS: ' + callParams.mediaSwitchURI);
2454
2522
  } else {
2455
2523
  // Request specific media switch to group calls on same server
2456
2524
  const otherSessions = this.getOtherSessions();
@@ -2458,24 +2526,41 @@ class Session extends EventEmitter {
2458
2526
  if (otherSessions.length > 0) {
2459
2527
  if (otherSessions[0]._mediaSwitch) {
2460
2528
  // There are existing active calls, append first media switch address found and request call grouping to this
2461
- this.log.debug("Requesting call grouping on media switch " + otherSessions[0]._mediaSwitch);
2462
- options.extraHeaders.push("X-RMS: sip:" + otherSessions[0]._mediaSwitch);
2529
+ this.log.debug('Requesting call grouping on media switch ' + otherSessions[0]._mediaSwitch);
2530
+ options.extraHeaders.push('X-RMS: sip:' + otherSessions[0]._mediaSwitch);
2463
2531
  } else {
2464
- this.log.warning("Other sessions exist but media switch address is invalid");
2532
+ this.log.warning('Other sessions exist but media switch address is invalid');
2465
2533
  }
2466
2534
  } else {
2467
- this.log.debug("No other active calls, no media switch grouping requested");
2535
+ this.log.debug('No other active calls, no media switch grouping requested');
2536
+ }
2537
+ }
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);
2468
2548
  }
2549
+ throw error;
2550
+ }
2551
+ for (const header of customHeaders) {
2552
+ this.log.debug('Adding custom header: ' + header);
2553
+ options.extraHeaders.push(header);
2469
2554
  }
2470
2555
 
2471
2556
  // Prepare invite uri
2472
- const uri = "sip:" + number + "@telegenta.com";
2557
+ const uri = 'sip:' + number + '@telegenta.com';
2473
2558
 
2474
2559
  // Not start call and create sip session
2475
2560
  try {
2476
2561
  this._initWebRTCSession(this._phone._UA.call(uri, options));
2477
2562
  } catch (error) {
2478
- this.log.error("Error starting call:", error);
2563
+ this.log.error('Error starting call:', error);
2479
2564
  return null;
2480
2565
  }
2481
2566
 
@@ -2486,16 +2571,16 @@ class Session extends EventEmitter {
2486
2571
 
2487
2572
  // Create new inbound session
2488
2573
  newInboundSession(newSessionRequest) {
2489
- this._callUUID = newSessionRequest.request.getHeader("X-P-UUID", 0);
2490
- this._destinationNumber = newSessionRequest.request.getHeader("X-DID", 0);
2491
- this._localNumberName = unescape(newSessionRequest.request.getHeader("X-DID-NAME", 0));
2574
+ this._callUUID = newSessionRequest.request.getHeader('X-P-UUID', 0);
2575
+ this._destinationNumber = newSessionRequest.request.getHeader('X-DID', 0);
2576
+ this._localNumberName = unescape(newSessionRequest.request.getHeader('X-DID-NAME', 0));
2492
2577
  this._originName = newSessionRequest.request.from.display_name;
2493
2578
  this._originatingNumber = newSessionRequest.request.from.uri.user;
2494
2579
  this.log.info(`Incoming call from ${this._originatingNumber} [${this._callUUID}]`);
2495
2580
  this.log.debug(newSessionRequest);
2496
2581
 
2497
2582
  // Get media switch info
2498
- this._mediaSwitch = newSessionRequest.request.getHeader("X-MS");
2583
+ this._mediaSwitch = newSessionRequest.request.getHeader('X-MS');
2499
2584
  this.log.debug(`Media switch handling call: ${this._mediaSwitch}`);
2500
2585
 
2501
2586
  this._initWebRTCSession(newSessionRequest.session);
@@ -2529,10 +2614,10 @@ class Session extends EventEmitter {
2529
2614
  const self = this;
2530
2615
 
2531
2616
  // ICE CANDIDATES
2532
- this._webRTCSession.on("icecandidate", function (event) {
2617
+ this._webRTCSession.on('icecandidate', function (event) {
2533
2618
  const c = event.candidate;
2534
2619
  self.log.debug(`Got ICE candidate: ${c.candidate}`);
2535
- if (c.type === "srflx" && c.relatedAddress !== null && c.relatedPort !== null) {
2620
+ if (c.type === 'srflx' && c.relatedAddress !== null && c.relatedPort !== null) {
2536
2621
  self.log.info(`Accepting ICE candidate: ${c.candidate}`);
2537
2622
 
2538
2623
  // Clear timer first
@@ -2555,24 +2640,24 @@ class Session extends EventEmitter {
2555
2640
  }
2556
2641
  });
2557
2642
 
2558
- this._webRTCSession.on("connecting", function (/* data */) {
2643
+ this._webRTCSession.on('connecting', function (/* data */) {
2559
2644
  self._startDurationUpdateTimer();
2560
2645
  self._startRTCPStatsUpdateTimer();
2561
- self._setSessionState("INITIALIZING");
2646
+ self._setSessionState('INITIALIZING');
2562
2647
  });
2563
2648
 
2564
- this._webRTCSession.on("sending", function (/* data */) {
2649
+ this._webRTCSession.on('sending', function (/* data */) {
2565
2650
  // Play call initialized tone if needed
2566
2651
  if (self._phone.options.playCallInitializedTone) {
2567
2652
  self._phone._startRepeatTone(ST_CALLINIT);
2568
2653
  }
2569
2654
 
2570
- self._setSessionState("CALLING");
2655
+ self._setSessionState('CALLING');
2571
2656
  });
2572
2657
 
2573
- this._webRTCSession.on("progress", function (data) {
2574
- if (data.originator === "remote") {
2575
- self._setSessionState("PROGRESS", { code: data.response.status_code });
2658
+ this._webRTCSession.on('progress', function (data) {
2659
+ if (data.originator === 'remote') {
2660
+ self._setSessionState('PROGRESS', { code: data.response.status_code });
2576
2661
  self._phone._endRepeatTone();
2577
2662
 
2578
2663
  // Ringback handling on 180 RINGING
@@ -2587,14 +2672,14 @@ class Session extends EventEmitter {
2587
2672
  try {
2588
2673
  self.hangup();
2589
2674
  } catch (e) {
2590
- self.log.info("No active session, ignoring answer call timeout");
2675
+ self.log.info('No active session, ignoring answer call timeout');
2591
2676
  }
2592
2677
  }, self._answerCallTimeout * 1000);
2593
2678
  }
2594
2679
  }
2595
2680
  });
2596
2681
 
2597
- this._webRTCSession.on("accepted", function (data) {
2682
+ this._webRTCSession.on('accepted', function (data) {
2598
2683
  // Always end ringsback
2599
2684
  self._phone._endRepeatTone();
2600
2685
 
@@ -2607,27 +2692,27 @@ class Session extends EventEmitter {
2607
2692
  }
2608
2693
 
2609
2694
  // Get initial recording state
2610
- if (data.originator === "remote") {
2611
- const recState = data.response.getHeader("X-Recording");
2612
- if (recState === "true") {
2613
- self.log.debug("Call is being prerecorded");
2695
+ if (data.originator === 'remote') {
2696
+ const recState = data.response.getHeader('X-Recording');
2697
+ if (recState === 'true') {
2698
+ self.log.debug('Call is being prerecorded');
2614
2699
  // self._isRecording = true;
2615
2700
  // self.emit('recordingStateChange', {state: true, error: null});
2616
2701
  } else {
2617
- self.log.debug("No call recording started yet");
2702
+ self.log.debug('No call recording started yet');
2618
2703
  }
2619
2704
 
2620
2705
  // Get media switch info
2621
- self._mediaSwitch = data.response.getHeader("X-MS");
2706
+ self._mediaSwitch = data.response.getHeader('X-MS');
2622
2707
  self.log.debug(`Media switch handling call: ${self._mediaSwitch}`);
2623
2708
  }
2624
2709
  });
2625
2710
 
2626
- this._webRTCSession.on("confirmed", function (/* data */) {
2627
- self._setSessionState("ACTIVE");
2711
+ this._webRTCSession.on('confirmed', function (/* data */) {
2712
+ self._setSessionState('ACTIVE');
2628
2713
  });
2629
2714
 
2630
- this._webRTCSession.on("ended", function (/* data */) {
2715
+ this._webRTCSession.on('ended', function (/* data */) {
2631
2716
  // End any ringsbacks if needed
2632
2717
  self._phone._endRepeatTone();
2633
2718
 
@@ -2638,10 +2723,10 @@ class Session extends EventEmitter {
2638
2723
  clearTimeout(self._answerCallTimer);
2639
2724
  }
2640
2725
 
2641
- self._setSessionState("TERMINATED");
2726
+ self._setSessionState('TERMINATED');
2642
2727
  });
2643
2728
 
2644
- this._webRTCSession.on("failed", function (data) {
2729
+ this._webRTCSession.on('failed', function (data) {
2645
2730
  // End any ringsbacks if needed
2646
2731
  self._phone._endRepeatTone();
2647
2732
 
@@ -2659,54 +2744,54 @@ class Session extends EventEmitter {
2659
2744
  if (data.message) {
2660
2745
  m = `${data.message.status_code} ${data.message.reason_phrase}`;
2661
2746
  } else {
2662
- m = "";
2747
+ m = '';
2663
2748
  }
2664
2749
 
2665
- self._setSessionState("FAILED", { cause: data.cause, message: m });
2750
+ self._setSessionState('FAILED', { cause: data.cause, message: m });
2666
2751
  });
2667
2752
 
2668
- this._webRTCSession.on("dtmf", function (data) {
2669
- self.log.debug("Session DTMF", data);
2753
+ this._webRTCSession.on('dtmf', function (data) {
2754
+ self.log.debug('Session DTMF', data);
2670
2755
  });
2671
2756
 
2672
- this._webRTCSession.on("newInfo", function (data) {
2673
- self.log.debug("New session info", data);
2757
+ this._webRTCSession.on('newInfo', function (data) {
2758
+ self.log.debug('New session info', data);
2674
2759
  });
2675
2760
 
2676
- this._webRTCSession.on("hold", function (/* data */) {
2761
+ this._webRTCSession.on('hold', function (/* data */) {
2677
2762
  self._isOnHold = true;
2678
2763
  self._fireHoldStateEvent();
2679
- self._setSessionState("ON_HOLD");
2764
+ self._setSessionState('ON_HOLD');
2680
2765
  });
2681
2766
 
2682
- this._webRTCSession.on("unhold", function (/* data */) {
2767
+ this._webRTCSession.on('unhold', function (/* data */) {
2683
2768
  self._isOnHold = false;
2684
2769
  self._fireHoldStateEvent();
2685
- self._setSessionState("ACTIVE");
2770
+ self._setSessionState('ACTIVE');
2686
2771
  });
2687
2772
 
2688
- this._webRTCSession.on("muted", function (data) {
2689
- self.log.debug("Microphone muted", data);
2773
+ this._webRTCSession.on('muted', function (data) {
2774
+ self.log.debug('Microphone muted', data);
2690
2775
  self._isMuted = true;
2691
2776
  self._fireMuteStateEvent();
2692
2777
  });
2693
2778
 
2694
- this._webRTCSession.on("unmuted", function (data) {
2695
- self.log.debug("Microphone unmuted", data);
2779
+ this._webRTCSession.on('unmuted', function (data) {
2780
+ self.log.debug('Microphone unmuted', data);
2696
2781
  self._isMuted = false;
2697
2782
  self._fireMuteStateEvent();
2698
2783
  });
2699
2784
 
2700
- this._webRTCSession.on("reinvite", function (data) {
2701
- self.log.debug("Session reinvite", data);
2785
+ this._webRTCSession.on('reinvite', function (data) {
2786
+ self.log.debug('Session reinvite', data);
2702
2787
  });
2703
2788
 
2704
- this._webRTCSession.on("update", function (data) {
2705
- self.log.debug("Session update", data);
2789
+ this._webRTCSession.on('update', function (data) {
2790
+ self.log.debug('Session update', data);
2706
2791
  });
2707
2792
 
2708
- this._webRTCSession.on("getusermediafailed", function (data) {
2709
- self.log.error("Getusermedia failed", data);
2793
+ this._webRTCSession.on('getusermediafailed', function (data) {
2794
+ self.log.error('Getusermedia failed', data);
2710
2795
  });
2711
2796
 
2712
2797
  // If connection connect audio stream
@@ -2715,7 +2800,7 @@ class Session extends EventEmitter {
2715
2800
  }
2716
2801
 
2717
2802
  // DEBUG STUFF
2718
- this.log.debug("WebRTCSession object:");
2803
+ this.log.debug('WebRTCSession object:');
2719
2804
  this.log.debug(this);
2720
2805
  }
2721
2806
 
@@ -2726,13 +2811,13 @@ class Session extends EventEmitter {
2726
2811
  */
2727
2812
  hangup(disposition = null) {
2728
2813
  if (!disposition) {
2729
- this.log.info("Hanging up call");
2814
+ this.log.info('Hanging up call');
2730
2815
  this._webRTCSession.terminate();
2731
2816
  } else {
2732
2817
  this.log.info(`Hanging up call with disposition ${disposition}`);
2733
2818
  this._manualDisposition = disposition;
2734
2819
  this._webRTCSession.terminate({
2735
- extraHeaders: ["X-HDISP: " + disposition],
2820
+ extraHeaders: ['X-HDISP: ' + disposition],
2736
2821
  });
2737
2822
  }
2738
2823
  }
@@ -2745,14 +2830,14 @@ class Session extends EventEmitter {
2745
2830
  let options = {
2746
2831
  mediaConstraints: { audio: true, video: false },
2747
2832
  pcConfig: {
2748
- iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }],
2833
+ iceServers: [{ urls: ['stun:stun.l.google.com:19302'] }],
2749
2834
  },
2750
- extraHeaders: ["X-Call-token: " + this._phone.options.callToken],
2835
+ extraHeaders: ['X-Call-token: ' + this._phone.options.callToken],
2751
2836
  };
2752
2837
 
2753
2838
  // Append meta info if needed
2754
2839
  if (metaInfo) {
2755
- options.extraHeaders.push("X-Meta-Info: " + encodeURI(metaInfo.toString().substr(0, 255)));
2840
+ options.extraHeaders.push('X-Meta-Info: ' + encodeURI(metaInfo.toString().substr(0, 255)));
2756
2841
  }
2757
2842
 
2758
2843
  this._webRTCSession.answer(options);
@@ -2766,8 +2851,8 @@ class Session extends EventEmitter {
2766
2851
  * @param statusCode - SIP Status code
2767
2852
  * @param reasonPhrase - Reason phrase
2768
2853
  */
2769
- reject(statusCode = 603, reasonPhrase = "Rejected call") {
2770
- this.log.info("Rejecting call");
2854
+ reject(statusCode = 603, reasonPhrase = 'Rejected call') {
2855
+ this.log.info('Rejecting call');
2771
2856
  this._webRTCSession.terminate({
2772
2857
  status_code: statusCode,
2773
2858
  reason_phrase: reasonPhrase,
@@ -2808,7 +2893,7 @@ class Session extends EventEmitter {
2808
2893
 
2809
2894
  // Set destination number
2810
2895
  set destinationNumber(number) {
2811
- this.log.debug("Setting destination number to: " + number);
2896
+ this.log.debug('Setting destination number to: ' + number);
2812
2897
  this._destinationNumber = number;
2813
2898
  }
2814
2899
 
@@ -2826,53 +2911,77 @@ class Session extends EventEmitter {
2826
2911
  */
2827
2912
  get direction() {
2828
2913
  if (this._isListenCall) {
2829
- this.log.debug("Call direction is LISTEN");
2830
- return "LISTEN";
2831
- } else if (this._webRTCSession.direction === "incoming") {
2832
- this.log.debug("Call direction is INBOUND");
2833
- return "INBOUND";
2834
- } else if (this._webRTCSession.direction === "outgoing") {
2835
- this.log.debug("Call direction is OUTBOUND");
2836
- return "OUTBOUND";
2914
+ this.log.debug('Call direction is LISTEN');
2915
+ return 'LISTEN';
2916
+ } else if (this._webRTCSession.direction === 'incoming') {
2917
+ this.log.debug('Call direction is INBOUND');
2918
+ return 'INBOUND';
2919
+ } else if (this._webRTCSession.direction === 'outgoing') {
2920
+ this.log.debug('Call direction is OUTBOUND');
2921
+ return 'OUTBOUND';
2837
2922
  } else {
2838
- this.log.error("Unknown call direction: " + this._webRTCSession.direction);
2839
- return "UNKNOWN";
2923
+ this.log.error('Unknown call direction: ' + this._webRTCSession.direction);
2924
+ return 'UNKNOWN';
2840
2925
  }
2841
2926
  }
2842
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
+
2843
2952
  // RECORDING
2844
2953
  /**
2845
2954
  * Start recording
2846
2955
  */
2847
2956
  startRecording() {
2848
2957
  const opt = {
2849
- extraHeaders: ["Record: on"],
2958
+ extraHeaders: ['Record: on'],
2850
2959
  };
2851
2960
 
2852
- this.log.debug("Request start recording");
2961
+ this.log.debug('Request start recording');
2853
2962
 
2854
2963
  const self = this;
2855
- this._webRTCSession.once("newInfo", (data) => {
2856
- if (data.originator === "local") {
2857
- data.info.on("succeeded", function (/* data */) {
2858
- self.log.info("Recording started successfully");
2964
+ this._webRTCSession.once('newInfo', (data) => {
2965
+ if (data.originator === 'local') {
2966
+ data.info.on('succeeded', function (/* data */) {
2967
+ self.log.info('Recording started successfully');
2859
2968
  self._isRecording = true;
2860
2969
  try {
2861
- self.emit("recordingStateChange", { state: true, error: null });
2970
+ self.emit('recordingStateChange', { state: true, error: null });
2862
2971
  } catch (e) {
2863
- self.log.error("Exception in recordingStateChange event: " + e);
2972
+ self.log.error('Exception in recordingStateChange event: ' + e);
2864
2973
  }
2865
2974
  });
2866
2975
 
2867
- data.info.on("failed", function (data) {
2976
+ data.info.on('failed', function (data) {
2868
2977
  self._recordingError = data.response.reason_phrase;
2869
- self.log.error("Recording start failed: " + self._recordingError);
2978
+ self.log.error('Recording start failed: ' + self._recordingError);
2870
2979
  self._fireRecordingStateEvent();
2871
2980
  });
2872
2981
  }
2873
2982
  });
2874
2983
 
2875
- this._webRTCSession.sendInfo("application/info", null, opt);
2984
+ this._webRTCSession.sendInfo('application/info', null, opt);
2876
2985
  }
2877
2986
 
2878
2987
  /**
@@ -2880,35 +2989,35 @@ class Session extends EventEmitter {
2880
2989
  */
2881
2990
  stopRecording() {
2882
2991
  const opt = {
2883
- extraHeaders: ["Record: off"],
2992
+ extraHeaders: ['Record: off'],
2884
2993
  };
2885
2994
 
2886
- this.log.debug("Request stop recording");
2995
+ this.log.debug('Request stop recording');
2887
2996
 
2888
2997
  const self = this;
2889
2998
 
2890
- this._webRTCSession.once("newInfo", (data) => {
2891
- data.info.on("succeeded", function (/* data */) {
2892
- self.log.info("Recording stopped successfully");
2999
+ this._webRTCSession.once('newInfo', (data) => {
3000
+ data.info.on('succeeded', function (/* data */) {
3001
+ self.log.info('Recording stopped successfully');
2893
3002
  self._isRecording = false;
2894
3003
  try {
2895
- self.emit("recordingStateChange", { state: false, error: null });
3004
+ self.emit('recordingStateChange', { state: false, error: null });
2896
3005
  } catch (e) {
2897
- self.log.error("Exception in recordingStateChange event: " + e);
3006
+ self.log.error('Exception in recordingStateChange event: ' + e);
2898
3007
  }
2899
3008
  });
2900
3009
 
2901
- data.info.on("failed", function (data) {
2902
- self.log.error("Recording stopped failed: " + data.response.reason_phrase);
3010
+ data.info.on('failed', function (data) {
3011
+ self.log.error('Recording stopped failed: ' + data.response.reason_phrase);
2903
3012
  try {
2904
- self.emit("recordingStateChange", { state: self._isRecording, error: data.response.reason_phrase });
3013
+ self.emit('recordingStateChange', { state: self._isRecording, error: data.response.reason_phrase });
2905
3014
  } catch (e) {
2906
- self.log.error("Exception in recordingStateChange event: " + e);
3015
+ self.log.error('Exception in recordingStateChange event: ' + e);
2907
3016
  }
2908
3017
  });
2909
3018
  });
2910
3019
 
2911
- this._webRTCSession.sendInfo("application/info", null, opt);
3020
+ this._webRTCSession.sendInfo('application/info', null, opt);
2912
3021
  }
2913
3022
 
2914
3023
  /**
@@ -2937,7 +3046,7 @@ class Session extends EventEmitter {
2937
3046
  * Mute microphone
2938
3047
  */
2939
3048
  muteMic() {
2940
- this.log.info("Mute microphone");
3049
+ this.log.info('Mute microphone');
2941
3050
  this._webRTCSession.mute();
2942
3051
  }
2943
3052
 
@@ -2945,7 +3054,7 @@ class Session extends EventEmitter {
2945
3054
  * Unmute microphone
2946
3055
  */
2947
3056
  unmuteMic() {
2948
- this.log.info("Unmute microphone");
3057
+ this.log.info('Unmute microphone');
2949
3058
  this._webRTCSession.unmute();
2950
3059
  }
2951
3060
 
@@ -2973,7 +3082,7 @@ class Session extends EventEmitter {
2973
3082
  * Hold call
2974
3083
  */
2975
3084
  hold() {
2976
- this.log.info("Hold call");
3085
+ this.log.info('Hold call');
2977
3086
  this._webRTCSession.hold();
2978
3087
  }
2979
3088
 
@@ -2981,7 +3090,7 @@ class Session extends EventEmitter {
2981
3090
  * Unhold call
2982
3091
  */
2983
3092
  unhold() {
2984
- this.log.info("Uncall call");
3093
+ this.log.info('Uncall call');
2985
3094
  this._phone.holdAllCalls();
2986
3095
  this._webRTCSession.unhold();
2987
3096
  }
@@ -3014,10 +3123,10 @@ class Session extends EventEmitter {
3014
3123
  sendDTMF(code, playLocal = true) {
3015
3124
  // Check for correct DTMF and get frequencies
3016
3125
  if (!(code in DTMF_FREQUENCY_TABLE)) {
3017
- throw new InvalidParameter("Invalid DTMF code");
3126
+ throw new InvalidParameter('Invalid DTMF code');
3018
3127
  }
3019
3128
 
3020
- this.log.info("Sending DTMF: " + code);
3129
+ this.log.info('Sending DTMF: ' + code);
3021
3130
  this._webRTCSession.sendDTMF(code);
3022
3131
 
3023
3132
  if (playLocal) {
@@ -3025,15 +3134,15 @@ class Session extends EventEmitter {
3025
3134
 
3026
3135
  // First mute microphone before playing DTMF
3027
3136
  if (this._isMuted) {
3028
- this.log.debug("Mic already muted, just sending DTMF");
3137
+ this.log.debug('Mic already muted, just sending DTMF');
3029
3138
  this._phone.playTone(500, f[0], f[1]);
3030
3139
  } else {
3031
- this.log.debug("Mute mic while sending DTMF");
3140
+ this.log.debug('Mute mic while sending DTMF');
3032
3141
  this._webRTCSession.mute();
3033
3142
 
3034
3143
  const self = this;
3035
3144
  this._phone.playTone(500, f[0], f[1], function () {
3036
- self.log.debug("Unmute mic after DTMF");
3145
+ self.log.debug('Unmute mic after DTMF');
3037
3146
  self._webRTCSession.unmute();
3038
3147
  });
3039
3148
  }
@@ -3049,20 +3158,22 @@ class Session extends EventEmitter {
3049
3158
  this.log.info(`Transferring ${this.uuid} => ${dstSession.uuid}`);
3050
3159
 
3051
3160
  // Create ReferSubscriber event class
3052
- const referSubscriber = this._webRTCSession.refer(this._webRTCSession._request.ruri, { replaces: dstSession._webRTCSession });
3161
+ const referSubscriber = this._webRTCSession.refer(this._webRTCSession._request.ruri, {
3162
+ replaces: dstSession._webRTCSession,
3163
+ });
3053
3164
 
3054
3165
  const self = this;
3055
3166
 
3056
3167
  // Setup some logging
3057
- referSubscriber.on("requestSucceeded", function () {
3058
- self.log.info("Transfer completed successfully");
3168
+ referSubscriber.on('requestSucceeded', function () {
3169
+ self.log.info('Transfer completed successfully');
3059
3170
  });
3060
3171
 
3061
- referSubscriber.on("requestFailed", function (cause) {
3172
+ referSubscriber.on('requestFailed', function (cause) {
3062
3173
  self.log.error(`Transfer rejected: ${cause}`);
3063
3174
  });
3064
3175
 
3065
- referSubscriber.on("failed", function (cause) {
3176
+ referSubscriber.on('failed', function (cause) {
3066
3177
  self.log.error(`Transfer failed: ${cause}`);
3067
3178
  });
3068
3179
  }
@@ -3080,14 +3191,14 @@ class Session extends EventEmitter {
3080
3191
  playback(media_file_id, hangup_after = false) {
3081
3192
  // Check event service is enabled first
3082
3193
  if (!this._phone._options.connectEventService) {
3083
- throw new NotAllowed("Event service not enabled");
3194
+ throw new NotAllowed('Event service not enabled');
3084
3195
  }
3085
3196
 
3086
3197
  this.log.info(`Playback media file ${media_file_id} hangup after ${hangup_after}`);
3087
3198
 
3088
- const p = this._phone._eventClient.fireEvent("call_command", {
3199
+ const p = this._phone._eventClient.fireEvent('call_command', {
3089
3200
  call_uuid: this.uuid,
3090
- call_command: "playback",
3201
+ call_command: 'playback',
3091
3202
  media_file_id: media_file_id,
3092
3203
  hangup_after: hangup_after,
3093
3204
  });
@@ -3103,7 +3214,7 @@ class Session extends EventEmitter {
3103
3214
  // When playback starts set state to playback
3104
3215
  p.then(() => {
3105
3216
  // Set session state
3106
- this._setSessionState("PLAYBACK");
3217
+ this._setSessionState('PLAYBACK');
3107
3218
  });
3108
3219
  }
3109
3220
 
@@ -3119,12 +3230,12 @@ class Session extends EventEmitter {
3119
3230
  * @throws InvalidOptions - If invalid mode given
3120
3231
  */
3121
3232
  setWhisperMode(mode = 0) {
3122
- if (!this.direction === "LISTEN") {
3123
- throw new NotAllowed("Call is not correct type (listen)");
3233
+ if (!this.direction === 'LISTEN') {
3234
+ throw new NotAllowed('Call is not correct type (listen)');
3124
3235
  }
3125
3236
 
3126
3237
  if (mode < 0 || mode > 2) {
3127
- throw new InvalidOptions("Invalid mode");
3238
+ throw new InvalidOptions('Invalid mode');
3128
3239
  }
3129
3240
 
3130
3241
  let t_dtmf_mode;
@@ -3182,13 +3293,13 @@ class Session extends EventEmitter {
3182
3293
  duration: Date.now() - self._callBeginTime,
3183
3294
  durationStr: createDurationString(Date.now() - self._callBeginTime),
3184
3295
  activeDuration: activeDuration,
3185
- activeDurationStr: activeDuration ? createDurationString(activeDuration) : "",
3296
+ activeDurationStr: activeDuration ? createDurationString(activeDuration) : '',
3186
3297
  };
3187
3298
 
3188
3299
  try {
3189
- self.emit("callDurationUpdate", e);
3300
+ self.emit('callDurationUpdate', e);
3190
3301
  } catch (e) {
3191
- self.log.error("Exception in call duration update event: " + e);
3302
+ self.log.error('Exception in call duration update event: ' + e);
3192
3303
  }
3193
3304
  }, 1000);
3194
3305
  }
@@ -3252,40 +3363,46 @@ class Session extends EventEmitter {
3252
3363
  for (let report of data.values()) {
3253
3364
  // this.log.debug(report);
3254
3365
  switch (report.type) {
3255
- case "outbound-rtp":
3366
+ case 'outbound-rtp':
3256
3367
  // this.log.debug('Got: outbound-rtp');
3257
3368
  // this.log.debug(report);
3258
3369
  // Packets
3259
3370
  totalReport.outboundPacketsSent = report.packetsSent;
3260
- deltaReport.outboundPacketsSent = report.packetsSent - this._currentReport.outboundPacketsSent;
3371
+ deltaReport.outboundPacketsSent =
3372
+ report.packetsSent - this._currentReport.outboundPacketsSent;
3261
3373
  // Bytes
3262
3374
  totalReport.outboundBytesSent = report.bytesSent;
3263
- deltaReport.outboundBytesSent = report.bytesSent - this._currentReport.outboundBytesSent;
3375
+ deltaReport.outboundBytesSent =
3376
+ report.bytesSent - this._currentReport.outboundBytesSent;
3264
3377
  break;
3265
3378
 
3266
- case "inbound-rtp":
3379
+ case 'inbound-rtp':
3267
3380
  // this.log.debug('Got: inbound-rtp');
3268
3381
  // this.log.debug(report);
3269
3382
  // Packets
3270
3383
  totalReport.inboundPacketsReceived = report.packetsReceived;
3271
- deltaReport.inboundPacketsReceived = report.packetsReceived - this._currentReport.inboundPacketsReceived;
3384
+ deltaReport.inboundPacketsReceived =
3385
+ report.packetsReceived - this._currentReport.inboundPacketsReceived;
3272
3386
  // Bytes
3273
3387
  totalReport.inboundBytesReceived = report.bytesReceived;
3274
- deltaReport.inboundBytesReceived = report.bytesReceived - this._currentReport.inboundBytesReceived;
3388
+ deltaReport.inboundBytesReceived =
3389
+ report.bytesReceived - this._currentReport.inboundBytesReceived;
3275
3390
  // Loss
3276
3391
  totalReport.inboundPacketsLost = report.packetsLost;
3277
- deltaReport.inboundPacketsLost = report.packetsLost - this._currentReport.inboundPacketsLost;
3392
+ deltaReport.inboundPacketsLost =
3393
+ report.packetsLost - this._currentReport.inboundPacketsLost;
3278
3394
  // Jitter
3279
3395
  totalReport.inboundJitter = report.jitter * 1000;
3280
3396
  deltaReport.inboundJitter = report.jitter * 1000;
3281
3397
  break;
3282
3398
 
3283
- case "remote-inbound-rtp":
3399
+ case 'remote-inbound-rtp':
3284
3400
  // this.log.debug('Got: remote-inbound-rtp');
3285
3401
  // this.log.debug(report);
3286
3402
  // Loss
3287
3403
  totalReport.outboundPacketsLost = report.packetsLost;
3288
- deltaReport.outboundPacketsLost = report.packetsLost - this._currentReport.outboundPacketsLost;
3404
+ deltaReport.outboundPacketsLost =
3405
+ report.packetsLost - this._currentReport.outboundPacketsLost;
3289
3406
  // Jitter
3290
3407
  totalReport.outboundJitter = report.jitter * 1000;
3291
3408
  deltaReport.outboundJitter = report.jitter * 1000;
@@ -3307,9 +3424,9 @@ class Session extends EventEmitter {
3307
3424
  };
3308
3425
 
3309
3426
  try {
3310
- this.emit("callQualityReportUpdate", finalReport);
3427
+ this.emit('callQualityReportUpdate', finalReport);
3311
3428
  } catch (e) {
3312
- this.log.error("Exception in call quality report update event: " + e);
3429
+ this.log.error('Exception in call quality report update event: ' + e);
3313
3430
  }
3314
3431
  }
3315
3432
  });
@@ -3333,27 +3450,28 @@ class Session extends EventEmitter {
3333
3450
  // this.log.debug(`R factor loss effect ${lossEffect} / latency effect ${latencyEffect}`);
3334
3451
 
3335
3452
  report.rFactor = Math.max(0, 100 - lossEffect - latencyEffect);
3336
- report.mos = 1 + 0.035 * report.rFactor + 0.000007 * report.rFactor * (report.rFactor - 60) * (100 - report.rFactor);
3453
+ report.mos =
3454
+ 1 + 0.035 * report.rFactor + 0.000007 * report.rFactor * (report.rFactor - 60) * (100 - report.rFactor);
3337
3455
 
3338
3456
  // Quality string
3339
3457
  switch (true) {
3340
3458
  case report.rFactor < 50:
3341
- report.qualityString = "Bad";
3459
+ report.qualityString = 'Bad';
3342
3460
  break;
3343
3461
  case report.rFactor < 70:
3344
- report.qualityString = "Poor";
3462
+ report.qualityString = 'Poor';
3345
3463
  break;
3346
3464
  case report.rFactor < 80:
3347
- report.qualityString = "Fair";
3465
+ report.qualityString = 'Fair';
3348
3466
  break;
3349
3467
  case report.rFactor < 90:
3350
- report.qualityString = "Good";
3468
+ report.qualityString = 'Good';
3351
3469
  break;
3352
3470
  case report.rFactor <= 100:
3353
- report.qualityString = "Excellent";
3471
+ report.qualityString = 'Excellent';
3354
3472
  break;
3355
3473
  default:
3356
- report.qualityString = "Error";
3474
+ report.qualityString = 'Error';
3357
3475
  }
3358
3476
 
3359
3477
  return report;
@@ -3387,7 +3505,7 @@ class Session extends EventEmitter {
3387
3505
  *
3388
3506
  */
3389
3507
 
3390
- this.log.debug("Session state: " + state);
3508
+ this.log.debug('Session state: ' + state);
3391
3509
  if (info) {
3392
3510
  this.log.debug(info);
3393
3511
  }
@@ -3395,7 +3513,7 @@ class Session extends EventEmitter {
3395
3513
  this._sessionState = state;
3396
3514
  try {
3397
3515
  this.emit(
3398
- "sessionStateChange",
3516
+ 'sessionStateChange',
3399
3517
  Object.assign(
3400
3518
  {
3401
3519
  state: state,
@@ -3405,51 +3523,51 @@ class Session extends EventEmitter {
3405
3523
  ),
3406
3524
  );
3407
3525
  } catch (e) {
3408
- this.log.error("Exception in sessionStateChange event:" + e);
3526
+ this.log.error('Exception in sessionStateChange event:' + e);
3409
3527
  }
3410
3528
 
3411
3529
  // Trigger callDataAvailable dummy event
3412
- if (state === "TERMINATED") {
3413
- this._callDisposition = this._manualDisposition || "NORMAL";
3530
+ if (state === 'TERMINATED') {
3531
+ this._callDisposition = this._manualDisposition || 'NORMAL';
3414
3532
  this._callFinished();
3415
3533
  }
3416
3534
 
3417
- if (state === "FAILED") {
3418
- if (info.cause === "Unavailable") {
3419
- this._callDisposition = "TEMP_UNAVAIL";
3535
+ if (state === 'FAILED') {
3536
+ if (info.cause === 'Unavailable') {
3537
+ this._callDisposition = 'TEMP_UNAVAIL';
3420
3538
  if (this._phone.options.playSignalTones) {
3421
3539
  this._phone._startRepeatTone(ST_TEMP_UNAVAIL);
3422
3540
  }
3423
3541
  }
3424
3542
 
3425
- if (info.cause === "Not Found") {
3426
- this._callDisposition = "INVALID_NUMBER";
3543
+ if (info.cause === 'Not Found') {
3544
+ this._callDisposition = 'INVALID_NUMBER';
3427
3545
  if (this._phone.options.playSignalTones) {
3428
3546
  this._phone._startRepeatTone(ST_INVALID_NUMBER);
3429
3547
  }
3430
3548
  }
3431
3549
 
3432
- if (info.cause === "Canceled") {
3433
- this._callDisposition = this._manualDisposition || "NO_ANSWER";
3550
+ if (info.cause === 'Canceled') {
3551
+ this._callDisposition = this._manualDisposition || 'NO_ANSWER';
3434
3552
  }
3435
3553
 
3436
- if (info.cause === "Rejected") {
3437
- if (this.direction === "INBOUND") {
3438
- this._callDisposition = "REJECTED";
3554
+ if (info.cause === 'Rejected') {
3555
+ if (this.direction === 'INBOUND') {
3556
+ this._callDisposition = 'REJECTED';
3439
3557
  } else {
3440
- this._callDisposition = "BARRED";
3558
+ this._callDisposition = 'BARRED';
3441
3559
  }
3442
3560
  }
3443
3561
 
3444
- if (info.cause === "Busy") {
3445
- this._callDisposition = "BUSY";
3562
+ if (info.cause === 'Busy') {
3563
+ this._callDisposition = 'BUSY';
3446
3564
  if (this._phone.options.playSignalTones) {
3447
3565
  this._phone._startRepeatTone(ST_BUSY);
3448
3566
  }
3449
3567
  }
3450
3568
 
3451
3569
  // If session is incoming and failed, we don't fire calldataAvailable event
3452
- if (this._webRTCSession.direction === "incoming") ;
3570
+ if (this._webRTCSession.direction === 'incoming') ;
3453
3571
 
3454
3572
  this._callFinished();
3455
3573
  }
@@ -3477,7 +3595,7 @@ class Session extends EventEmitter {
3477
3595
  * @property {string} REJECTED Incoming call was rejected
3478
3596
  */
3479
3597
  _callFinished() {
3480
- this.log.info("Call finished");
3598
+ this.log.info('Call finished');
3481
3599
 
3482
3600
  // Fire callEnded event
3483
3601
  this._fireCallEndedEvent();
@@ -3494,7 +3612,7 @@ class Session extends EventEmitter {
3494
3612
 
3495
3613
  // Set answer call timeout
3496
3614
  _setAnswerCallTimeout(timeout) {
3497
- this.log.debug("Setting answer call timeout to: " + timeout);
3615
+ this.log.debug('Setting answer call timeout to: ' + timeout);
3498
3616
  this._answerCallTimeout = timeout;
3499
3617
  }
3500
3618
 
@@ -3506,9 +3624,9 @@ class Session extends EventEmitter {
3506
3624
  * @property {boolean} state - New recording state
3507
3625
  */
3508
3626
  try {
3509
- this.emit("recordingStateChange", { state: this._isRecording, error: this._recordingError });
3627
+ this.emit('recordingStateChange', { state: this._isRecording, error: this._recordingError });
3510
3628
  } catch (e) {
3511
- this.log.error("Exception in recording state change event: " + e);
3629
+ this.log.error('Exception in recording state change event: ' + e);
3512
3630
  }
3513
3631
  }
3514
3632
 
@@ -3520,9 +3638,9 @@ class Session extends EventEmitter {
3520
3638
  * @property {boolean} state - Mute state (true = muted / false = unmuted)
3521
3639
  */
3522
3640
  try {
3523
- this.emit("muteStateChange", { state: this._isMuted });
3641
+ this.emit('muteStateChange', { state: this._isMuted });
3524
3642
  } catch (e) {
3525
- this.log.error("Exception in mute state change event: " + e);
3643
+ this.log.error('Exception in mute state change event: ' + e);
3526
3644
  }
3527
3645
  }
3528
3646
 
@@ -3534,9 +3652,9 @@ class Session extends EventEmitter {
3534
3652
  * @property {boolean} state - New hold state
3535
3653
  */
3536
3654
  try {
3537
- this.emit("holdStateChange", { state: this._isOnHold });
3655
+ this.emit('holdStateChange', { state: this._isOnHold });
3538
3656
  } catch (e) {
3539
- this.log.error("Exception in hold state change event: " + e);
3657
+ this.log.error('Exception in hold state change event: ' + e);
3540
3658
  }
3541
3659
  }
3542
3660
 
@@ -3548,9 +3666,9 @@ class Session extends EventEmitter {
3548
3666
  * @property {boolean} mode - New whisper mode (0 = No whisper / 1 = Local only / 2 = Three way)
3549
3667
  */
3550
3668
  try {
3551
- this.emit("whisperModeChange", { mode: this._whisperMode });
3669
+ this.emit('whisperModeChange', { mode: this._whisperMode });
3552
3670
  } catch (e) {
3553
- this.log.error("Exception in whisper mode change event: " + e);
3671
+ this.log.error('Exception in whisper mode change event: ' + e);
3554
3672
  }
3555
3673
  }
3556
3674
 
@@ -3599,14 +3717,14 @@ class Session extends EventEmitter {
3599
3717
 
3600
3718
  // Add some extra convenience fields
3601
3719
  e.durationStr = createDurationString(e.duration);
3602
- e.answerDurationStr = e.answerDuration ? createDurationString(e.answerDuration) : "";
3603
- e.activeDurationStr = e.activeDuration ? createDurationString(e.activeDuration) : "";
3720
+ e.answerDurationStr = e.answerDuration ? createDurationString(e.answerDuration) : '';
3721
+ e.activeDurationStr = e.activeDuration ? createDurationString(e.activeDuration) : '';
3604
3722
  e.callQualityReport = this._currentReport;
3605
3723
 
3606
3724
  try {
3607
- this.emit("callEnded", e);
3725
+ this.emit('callEnded', e);
3608
3726
  } catch (e) {
3609
- this.log.error("Exception in callEnded event: " + e);
3727
+ this.log.error('Exception in callEnded event: ' + e);
3610
3728
  }
3611
3729
  }
3612
3730
 
@@ -3617,11 +3735,11 @@ class Session extends EventEmitter {
3617
3735
 
3618
3736
  const self = this;
3619
3737
  session.connection.ontrack = function (event) {
3620
- self.log.debug("Start media output (track):");
3738
+ self.log.debug('Start media output (track):');
3621
3739
  self.log.debug(event);
3622
3740
  self._phone._remoteAudioElement.srcObject = event.streams[0];
3623
3741
  };
3624
3742
  }
3625
3743
  }
3626
3744
 
3627
- export { EventClient, 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 };