@wazo/sdk 0.43.6 → 1.0.0-alpha.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.
Files changed (141) hide show
  1. package/dist/wazo-sdk.js +2 -2
  2. package/esm/__tests__/__voice__/call.test.js +72 -0
  3. package/esm/__tests__/__voice__/softphone.test.js +66 -0
  4. package/esm/checker/checks/webrtc.js +2 -1
  5. package/esm/domain/InvalidState.js +8 -0
  6. package/esm/domain/InvalidStateTransition.js +10 -0
  7. package/esm/domain/Phone/WebRTCPhone.js +33 -35
  8. package/esm/index.js +2 -0
  9. package/esm/simple/Phone.js +2 -1
  10. package/esm/simple/index.js +8 -2
  11. package/esm/simple/room/Room.js +2 -1
  12. package/esm/simple/room/SipRoom.js +2 -1
  13. package/esm/state-machine/call-state-machine.js +114 -0
  14. package/esm/state-machine/softphone-state-machine.js +39 -0
  15. package/esm/state-machine/utils.js +40 -0
  16. package/esm/types/__tests__/__voice__/call.test.d.ts +2 -0
  17. package/esm/types/__tests__/__voice__/call.test.d.ts.map +1 -0
  18. package/esm/types/__tests__/__voice__/softphone.test.d.ts +2 -0
  19. package/esm/types/__tests__/__voice__/softphone.test.d.ts.map +1 -0
  20. package/esm/types/checker/checks/webrtc.d.ts.map +1 -1
  21. package/esm/types/domain/InvalidState.d.ts +6 -0
  22. package/esm/types/domain/InvalidState.d.ts.map +1 -0
  23. package/esm/types/domain/InvalidStateTransition.d.ts +7 -0
  24. package/esm/types/domain/InvalidStateTransition.d.ts.map +1 -0
  25. package/esm/types/domain/Phone/WebRTCPhone.d.ts +0 -1
  26. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  27. package/esm/types/domain/types.d.ts +4 -0
  28. package/esm/types/domain/types.d.ts.map +1 -1
  29. package/esm/types/index.d.ts +2 -0
  30. package/esm/types/index.d.ts.map +1 -1
  31. package/esm/types/simple/Phone.d.ts.map +1 -1
  32. package/esm/types/simple/index.d.ts +7 -2
  33. package/esm/types/simple/index.d.ts.map +1 -1
  34. package/esm/types/simple/room/Room.d.ts.map +1 -1
  35. package/esm/types/simple/room/SipRoom.d.ts +1 -1
  36. package/esm/types/simple/room/SipRoom.d.ts.map +1 -1
  37. package/esm/types/state-machine/call-state-machine.d.ts +43 -0
  38. package/esm/types/state-machine/call-state-machine.d.ts.map +1 -0
  39. package/esm/types/state-machine/softphone-state-machine.d.ts +19 -0
  40. package/esm/types/state-machine/softphone-state-machine.d.ts.map +1 -0
  41. package/esm/types/state-machine/utils.d.ts +9 -0
  42. package/esm/types/state-machine/utils.d.ts.map +1 -0
  43. package/esm/types/utils/sdp.d.ts +6 -0
  44. package/esm/types/utils/sdp.d.ts.map +1 -1
  45. package/esm/types/utils/sip-logger.d.ts +4 -0
  46. package/esm/types/utils/sip-logger.d.ts.map +1 -0
  47. package/esm/types/utils/webrtc.d.ts +40 -0
  48. package/esm/types/utils/webrtc.d.ts.map +1 -1
  49. package/esm/types/voice/call.d.ts +110 -0
  50. package/esm/types/voice/call.d.ts.map +1 -0
  51. package/esm/types/voice/softphone.d.ts +77 -0
  52. package/esm/types/voice/softphone.d.ts.map +1 -0
  53. package/esm/types/web-rtc-client.d.ts +1 -36
  54. package/esm/types/web-rtc-client.d.ts.map +1 -1
  55. package/esm/utils/sdp.js +26 -0
  56. package/esm/utils/sip-logger.js +32 -0
  57. package/esm/utils/webrtc.js +465 -0
  58. package/esm/voice/call.js +527 -0
  59. package/esm/voice/softphone.js +347 -0
  60. package/esm/web-rtc-client.js +86 -584
  61. package/lib/__tests__/__voice__/call.test.js +86 -0
  62. package/lib/__tests__/__voice__/softphone.test.js +80 -0
  63. package/lib/checker/checks/webrtc.js +2 -1
  64. package/lib/domain/InvalidState.js +9 -0
  65. package/lib/domain/InvalidStateTransition.js +10 -0
  66. package/lib/domain/Phone/WebRTCPhone.js +33 -35
  67. package/lib/index.js +6 -2
  68. package/lib/simple/Phone.js +2 -1
  69. package/lib/simple/index.js +6 -0
  70. package/lib/simple/room/Room.js +4 -3
  71. package/lib/simple/room/SipRoom.js +2 -1
  72. package/lib/state-machine/call-state-machine.js +120 -0
  73. package/lib/state-machine/softphone-state-machine.js +42 -0
  74. package/lib/state-machine/utils.js +52 -0
  75. package/lib/types/__tests__/__voice__/call.test.d.ts +2 -0
  76. package/lib/types/__tests__/__voice__/call.test.d.ts.map +1 -0
  77. package/lib/types/__tests__/__voice__/softphone.test.d.ts +2 -0
  78. package/lib/types/__tests__/__voice__/softphone.test.d.ts.map +1 -0
  79. package/lib/types/checker/checks/webrtc.d.ts.map +1 -1
  80. package/lib/types/domain/InvalidState.d.ts +6 -0
  81. package/lib/types/domain/InvalidState.d.ts.map +1 -0
  82. package/lib/types/domain/InvalidStateTransition.d.ts +7 -0
  83. package/lib/types/domain/InvalidStateTransition.d.ts.map +1 -0
  84. package/lib/types/domain/Phone/WebRTCPhone.d.ts +0 -1
  85. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  86. package/lib/types/domain/types.d.ts +4 -0
  87. package/lib/types/domain/types.d.ts.map +1 -1
  88. package/lib/types/index.d.ts +2 -0
  89. package/lib/types/index.d.ts.map +1 -1
  90. package/lib/types/simple/Phone.d.ts.map +1 -1
  91. package/lib/types/simple/index.d.ts +7 -2
  92. package/lib/types/simple/index.d.ts.map +1 -1
  93. package/lib/types/simple/room/Room.d.ts.map +1 -1
  94. package/lib/types/simple/room/SipRoom.d.ts +1 -1
  95. package/lib/types/simple/room/SipRoom.d.ts.map +1 -1
  96. package/lib/types/state-machine/call-state-machine.d.ts +43 -0
  97. package/lib/types/state-machine/call-state-machine.d.ts.map +1 -0
  98. package/lib/types/state-machine/softphone-state-machine.d.ts +19 -0
  99. package/lib/types/state-machine/softphone-state-machine.d.ts.map +1 -0
  100. package/lib/types/state-machine/utils.d.ts +9 -0
  101. package/lib/types/state-machine/utils.d.ts.map +1 -0
  102. package/lib/types/utils/sdp.d.ts +6 -0
  103. package/lib/types/utils/sdp.d.ts.map +1 -1
  104. package/lib/types/utils/sip-logger.d.ts +4 -0
  105. package/lib/types/utils/sip-logger.d.ts.map +1 -0
  106. package/lib/types/utils/webrtc.d.ts +40 -0
  107. package/lib/types/utils/webrtc.d.ts.map +1 -1
  108. package/lib/types/voice/call.d.ts +110 -0
  109. package/lib/types/voice/call.d.ts.map +1 -0
  110. package/lib/types/voice/softphone.d.ts +77 -0
  111. package/lib/types/voice/softphone.d.ts.map +1 -0
  112. package/lib/types/web-rtc-client.d.ts +1 -36
  113. package/lib/types/web-rtc-client.d.ts.map +1 -1
  114. package/lib/utils/sdp.js +27 -1
  115. package/lib/utils/sip-logger.js +37 -0
  116. package/lib/utils/webrtc.js +521 -1
  117. package/lib/voice/call.js +577 -0
  118. package/lib/voice/softphone.js +375 -0
  119. package/lib/web-rtc-client.js +89 -601
  120. package/package.json +3 -2
  121. package/src/__tests__/__voice__/call.test.ts +90 -0
  122. package/src/__tests__/__voice__/softphone.test.ts +82 -0
  123. package/src/checker/checks/webrtc.ts +2 -1
  124. package/src/domain/InvalidState.ts +13 -0
  125. package/src/domain/InvalidStateTransition.ts +17 -0
  126. package/src/domain/Phone/WebRTCPhone.ts +34 -36
  127. package/src/domain/types.ts +6 -1
  128. package/src/index.ts +2 -0
  129. package/src/simple/Phone.ts +2 -1
  130. package/src/simple/index.ts +9 -2
  131. package/src/simple/room/Room.ts +2 -1
  132. package/src/simple/room/SipRoom.ts +2 -1
  133. package/src/state-machine/call-state-machine.ts +131 -0
  134. package/src/state-machine/softphone-state-machine.ts +49 -0
  135. package/src/state-machine/utils.ts +53 -0
  136. package/src/utils/sdp.ts +36 -0
  137. package/src/utils/sip-logger.ts +40 -0
  138. package/src/utils/webrtc.ts +571 -0
  139. package/src/voice/call.ts +713 -0
  140. package/src/voice/softphone.ts +485 -0
  141. package/src/web-rtc-client.ts +90 -690
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable class-methods-use-this, no-param-reassign, max-classes-per-file, no-underscore-dangle */
2
- /* global window, document, navigator */
2
+ /* global document, navigator */
3
3
  import 'webrtc-adapter';
4
4
  import { UserAgentState } from 'sip.js/lib/api/user-agent-state';
5
5
  import { Parser } from 'sip.js/lib/core/messages/parser';
@@ -11,23 +11,14 @@ import { Messager } from 'sip.js/lib/api/messager';
11
11
  import { RegistererState } from 'sip.js/lib/api/registerer-state';
12
12
  import { SessionState } from 'sip.js/lib/api/session-state';
13
13
  import { TransportState } from 'sip.js/lib/api/transport-state';
14
- import { defaultPeerConnectionConfiguration } from 'sip.js/lib/platform/web/session-description-handler/peer-connection-configuration-default';
15
14
  import getStats from 'getstats';
16
15
  import { Inviter, Invitation, Registerer } from 'sip.js/lib/api';
17
- import WazoSessionDescriptionHandler, { wazoMediaStreamFactory } from './lib/WazoSessionDescriptionHandler';
18
16
  import Emitter from './utils/Emitter';
19
17
  import ApiClient from './api-client';
20
18
  import IssueReporter from './service/IssueReporter';
21
19
  import Heartbeat from './utils/Heartbeat';
22
- import { getVideoDirection, hasAnActiveVideo } from './utils/sdp';
23
- import { lastIndexOf } from './utils/array';
24
- // We need to replace 0.0.0.0 to 127.0.0.1 in the sdp to avoid MOH during a createOffer.
25
- export const replaceLocalIpModifier = (description) => Promise.resolve({
26
- ...JSON.parse(JSON.stringify(description)),
27
- sdp: description.sdp.replace('c=IN IP4 0.0.0.0', 'c=IN IP4 127.0.0.1'),
28
- });
29
- const SIP_ID_LENGTH = 36;
30
- const DEFAULT_ICE_TIMEOUT = 3000;
20
+ import { getSipSessionId, makeURI, replaceLocalIpModifier, SIP_ID_LENGTH } from './utils/sdp';
21
+ import { cleanupStream, createUaOptions, fetchNetworkStats, getAudioConstraints, getLocalStream, getLocalTracks, getLocalVideoStream, getMediaConfiguration, getPeerConnection, getRemoteStream, getRemoteTracks, getRemoteVideoStream, getRemoteVideoStreamFromPc, getVideoConstraints, hasALocalVideoTrack, hasARemoteVideoTrack, hasAVideoTrack, hasLocalVideo, hasRemoteVideo, hasVideoTrack, isAudioOnly, isVideoRemotelyHeld, isWeb, sessionWantsToDoVideo, setLocalMediaStream, toggleAudio, toggleVideo, updateRemoteStream } from './utils/webrtc';
31
22
  const SEND_STATS_DELAY = 5000;
32
23
  const states = ['STATUS_NULL', 'STATUS_NEW', 'STATUS_CONNECTING', 'STATUS_CONNECTED', 'STATUS_COMPLETED'];
33
24
  const logger = IssueReporter ? IssueReporter.loggerFor('webrtc-client') : console;
@@ -56,8 +47,6 @@ export const transportEvents = [CONNECTED, DISCONNECTED, TRANSPORT_ERROR, MESSAG
56
47
  export class CanceledCallError extends Error {
57
48
  }
58
49
  const MAX_REGISTER_TRIES = 5;
59
- // setting a 24hr timeout and letting the backend define the actual value
60
- const NO_ANSWER_TIMEOUT = 60 * 60 * 24; // in seconds
61
50
  export default class WebRTCClient extends Emitter {
62
51
  clientId;
63
52
  config;
@@ -104,18 +93,6 @@ export default class WebRTCClient extends Emitter {
104
93
  ON_EARLY_MEDIA;
105
94
  ON_PROGRESS;
106
95
  ON_DISCONNECTED;
107
- static isAPrivateIp(ip) {
108
- const regex = /^(?:10|127|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\..*/;
109
- return regex.exec(ip) == null;
110
- }
111
- static getIceServers(ip) {
112
- if (WebRTCClient.isAPrivateIp(ip)) {
113
- return [{
114
- urls: ['stun:stun.l.google.com:19302', 'stun:stun4.l.google.com:19302'],
115
- }];
116
- }
117
- return [];
118
- }
119
96
  constructor(config, session, uaConfigOverrides) {
120
97
  super();
121
98
  // For debug purpose
@@ -179,7 +156,7 @@ export default class WebRTCClient extends Emitter {
179
156
  this.audio = media.audio;
180
157
  }
181
158
  createUserAgent(uaConfigOverrides) {
182
- const uaOptions = this._createUaOptions(uaConfigOverrides);
159
+ const uaOptions = createUaOptions(this.config, uaConfigOverrides, this.audio, this.video);
183
160
  logger.info('sdk webrtc, creating UA', {
184
161
  uaOptions: { ...uaOptions, authorizationPassword: `${uaOptions?.authorizationPassword?.slice(0, 5)}xxxx` },
185
162
  clientId: this.clientId,
@@ -196,7 +173,7 @@ export default class WebRTCClient extends Emitter {
196
173
  });
197
174
  this._setupSession(invitation);
198
175
  const shouldAutoAnswer = !!invitation.request.getHeader('alert-info');
199
- this.eventEmitter.emit(INVITE, invitation, this.sessionWantsToDoVideo(invitation), shouldAutoAnswer);
176
+ this.eventEmitter.emit(INVITE, invitation, sessionWantsToDoVideo(invitation), shouldAutoAnswer);
200
177
  },
201
178
  };
202
179
  const ua = new UserAgent(uaOptions);
@@ -287,7 +264,7 @@ export default class WebRTCClient extends Emitter {
287
264
  logger.info('sdk webrtc registering aborted due to a registration in progress.', { clientId: this.clientId });
288
265
  return Promise.resolve();
289
266
  }
290
- const registerOptions = this._isWeb() ? {} : {
267
+ const registerOptions = isWeb() ? {} : {
291
268
  extraContactHeaderParams: ['mobility=mobile'],
292
269
  };
293
270
  const onRegisterFailed = () => {
@@ -445,7 +422,7 @@ export default class WebRTCClient extends Emitter {
445
422
  if (audioOnly) {
446
423
  inviterOptions.sessionDescriptionHandlerModifiersReInvite = [stripVideo];
447
424
  }
448
- const uri = this._makeURI(number);
425
+ const uri = makeURI(number, this.config.host);
449
426
  let session = null;
450
427
  if (uri) {
451
428
  session = this.userAgent ? new Inviter(this.userAgent, uri, inviterOptions) : null;
@@ -458,7 +435,7 @@ export default class WebRTCClient extends Emitter {
458
435
  this._setupSession(session);
459
436
  }
460
437
  if (conference) {
461
- this.conferences[this.getSipSessionId(session)] = true;
438
+ this.conferences[getSipSessionId(session)] = true;
462
439
  }
463
440
  const inviteOptions = {
464
441
  requestDelegate: {
@@ -482,7 +459,7 @@ export default class WebRTCClient extends Emitter {
482
459
  this.eventEmitter.emit(REJECTED, session, response);
483
460
  },
484
461
  },
485
- sessionDescriptionHandlerOptions: this.getMediaConfiguration(enableVideo || false, conference),
462
+ sessionDescriptionHandlerOptions: getMediaConfiguration(enableVideo || false, conference),
486
463
  };
487
464
  if (inviteOptions.sessionDescriptionHandlerOptions) {
488
465
  inviteOptions.sessionDescriptionHandlerOptions.audioOnly = audioOnly;
@@ -512,7 +489,7 @@ export default class WebRTCClient extends Emitter {
512
489
  return Promise.reject(new Error(error));
513
490
  }
514
491
  const options = {
515
- sessionDescriptionHandlerOptions: this.getMediaConfiguration(enableVideo || false),
492
+ sessionDescriptionHandlerOptions: getMediaConfiguration(enableVideo || false),
516
493
  };
517
494
  return this._accept(session, options).then(() => {
518
495
  // @ts-ignore: private
@@ -537,7 +514,7 @@ export default class WebRTCClient extends Emitter {
537
514
  try {
538
515
  this._stopSendingStats(session);
539
516
  this._cleanupMedia(session);
540
- delete this.sipSessions[this.getSipSessionId(session)];
517
+ delete this.sipSessions[getSipSessionId(session)];
541
518
  // Check if Invitation or Inviter (Invitation = incoming call)
542
519
  const isInviter = session instanceof Inviter;
543
520
  // @see github.com/onsip/SIP.js/blob/f11dfd584bc9788ccfc94e03034020672b738975/src/platform/web/simple-user/simple-user.ts#L1004
@@ -557,16 +534,9 @@ export default class WebRTCClient extends Emitter {
557
534
  }
558
535
  return Promise.resolve(null);
559
536
  }
560
- async getStats(session) {
561
- const pc = session.sessionDescriptionHandler?.peerConnection;
562
- if (!pc) {
563
- return null;
564
- }
565
- return pc.getStats(null);
566
- }
567
537
  // Fetch and emit an event at `interval` with session network stats
568
538
  startNetworkMonitoring(session, interval = 1000) {
569
- const sessionId = this.getSipSessionId(session);
539
+ const sessionId = getSipSessionId(session);
570
540
  logger.info('starting network inspection', {
571
541
  id: sessionId,
572
542
  clientId: this.clientId,
@@ -575,7 +545,7 @@ export default class WebRTCClient extends Emitter {
575
545
  this.networkMonitoringInterval[sessionId] = setInterval(() => this._fetchNetworkStats(sessionId), interval);
576
546
  }
577
547
  stopNetworkMonitoring(session) {
578
- const sessionId = this.getSipSessionId(session);
548
+ const sessionId = getSipSessionId(session);
579
549
  const exists = (sessionId in this.networkMonitoringInterval);
580
550
  logger.info('stopping network inspection', {
581
551
  clientId: this.clientId,
@@ -643,24 +613,17 @@ export default class WebRTCClient extends Emitter {
643
613
  this.userAgent = null;
644
614
  logger.info('sdk webrtc client closed', { clientId: this.clientId });
645
615
  }
646
- getNumber(session) {
647
- if (!session) {
648
- return null;
649
- }
650
- // @ts-ignore: private
651
- return session.remoteIdentity.uri._normal.user;
652
- }
653
616
  mute(session) {
654
617
  logger.info('sdk webrtc mute', {
655
618
  id: session.id,
656
619
  });
657
- this._toggleAudio(session, true);
620
+ toggleAudio(session, true);
658
621
  }
659
622
  unmute(session) {
660
623
  logger.info('sdk webrtc unmute', {
661
624
  id: session.id,
662
625
  });
663
- this._toggleAudio(session, false);
626
+ toggleAudio(session, false);
664
627
  }
665
628
  isAudioMuted(session) {
666
629
  if (!session || !session.sessionDescriptionHandler) {
@@ -697,16 +660,16 @@ export default class WebRTCClient extends Emitter {
697
660
  logger.info('sdk webrtc toggle camera on', {
698
661
  id: session.id,
699
662
  });
700
- this._toggleVideo(session, false);
663
+ toggleVideo(session, false);
701
664
  }
702
665
  toggleCameraOff(session) {
703
666
  logger.info('sdk webrtc toggle camera off', {
704
667
  id: session.id,
705
668
  });
706
- this._toggleVideo(session, true);
669
+ toggleVideo(session, true);
707
670
  }
708
671
  hold(session, isConference = false, hadVideo = false) {
709
- const sessionId = this.getSipSessionId(session);
672
+ const sessionId = getSipSessionId(session);
710
673
  const hasVideo = hadVideo || this.hasLocalVideo(sessionId);
711
674
  logger.info('sdk webrtc hold', {
712
675
  sessionId,
@@ -734,8 +697,8 @@ export default class WebRTCClient extends Emitter {
734
697
  hold: true,
735
698
  conference: isConference,
736
699
  };
737
- const options = this.getMediaConfiguration(false, isConference);
738
- if (!this._isWeb()) {
700
+ const options = getMediaConfiguration(false, isConference);
701
+ if (!isWeb()) {
739
702
  options.sessionDescriptionHandlerModifiers = [holdModifier];
740
703
  }
741
704
  options.sessionDescriptionHandlerOptions = {
@@ -754,7 +717,7 @@ export default class WebRTCClient extends Emitter {
754
717
  });
755
718
  }
756
719
  unhold(session, isConference = false) {
757
- const sessionId = this.getSipSessionId(session);
720
+ const sessionId = getSipSessionId(session);
758
721
  const hasVideo = sessionId in this.heldSessions && this.heldSessions[sessionId].hasVideo;
759
722
  logger.info('sdk webrtc unhold', {
760
723
  sessionId,
@@ -769,13 +732,13 @@ export default class WebRTCClient extends Emitter {
769
732
  return Promise.resolve();
770
733
  }
771
734
  this.unmute(session);
772
- delete this.heldSessions[this.getSipSessionId(session)];
735
+ delete this.heldSessions[getSipSessionId(session)];
773
736
  session.sessionDescriptionHandlerOptionsReInvite = {
774
737
  hold: false,
775
738
  conference: isConference,
776
739
  };
777
- const options = this.getMediaConfiguration(false, isConference);
778
- if (!this._isWeb()) {
740
+ const options = getMediaConfiguration(false, isConference);
741
+ if (!isWeb()) {
779
742
  // We should sent an empty `sessionDescriptionHandlerModifiers` or sip.js will take the last sent modifiers
780
743
  // (eg: holdModifier)
781
744
  options.sessionDescriptionHandlerModifiers = [];
@@ -790,105 +753,27 @@ export default class WebRTCClient extends Emitter {
790
753
  logger.warn('sdk webrtc re-invite during resume, error', e);
791
754
  });
792
755
  }
793
- // Returns true if a re-INVITE is required
794
- async upgradeToVideo(session, constraints, isConference) {
795
- const pc = session.sessionDescriptionHandler?.peerConnection;
796
- // Check if a video sender already exists
797
- let videoSender;
798
- if (isConference) {
799
- // We search for the last transceiver without `video-` in the mid (video- means remote transceiver)
800
- const transceivers = pc?.getTransceivers() || [];
801
- const transceiverIdx = lastIndexOf(transceivers, transceiver => transceiver.sender.track === null && transceiver.mid && transceiver.mid.indexOf('video') === -1);
802
- videoSender = transceiverIdx !== -1 ? transceivers[transceiverIdx].sender : null;
803
- }
804
- else {
805
- videoSender = pc && pc.getSenders && pc.getSenders().find((sender) => sender.track === null);
806
- }
807
- if (!videoSender) {
808
- // When no video sender found, it means that we're in the first video upgrade in 1:1
809
- return;
810
- }
811
- // Reuse bidirectional video stream
812
- const newStream = await this.getStreamFromConstraints(constraints);
813
- if (!newStream) {
814
- console.warn(`Can't create media stream with: ${JSON.stringify(constraints || {})}`);
815
- return;
816
- }
817
- // Add previous local audio track
818
- if (constraints && !constraints.audio) {
819
- const localVideoStream = session.sessionDescriptionHandler?.localMediaStream;
820
- const localAudioTrack = localVideoStream.getTracks().find(track => track.kind === 'audio');
821
- if (localAudioTrack) {
822
- newStream.addTrack(localAudioTrack);
823
- }
824
- }
825
- const videoTrack = newStream.getVideoTracks()[0];
826
- if (videoTrack) {
827
- videoSender.replaceTrack(videoTrack);
828
- }
829
- this.setLocalMediaStream(this.getSipSessionId(session), newStream);
830
- return newStream;
831
- }
832
- downgradeToAudio(session) {
833
- // Release local video stream when downgrading to audio
834
- const sessionDescriptionHandler = session.sessionDescriptionHandler;
835
- const localStream = sessionDescriptionHandler?.localMediaStream;
836
- const pc = sessionDescriptionHandler?.peerConnection;
837
- const videoTracks = localStream.getVideoTracks();
838
- // Remove video senders
839
- if (pc?.getSenders) {
840
- pc.getSenders().filter((sender) => sender.track && sender.track.kind === 'video').forEach((videoSender) => {
841
- videoSender.replaceTrack(null);
842
- });
843
- }
844
- videoTracks.forEach((videoTrack) => {
845
- videoTrack.enabled = false;
846
- videoTrack.stop();
847
- localStream.removeTrack(videoTrack);
848
- });
849
- }
850
- async getStreamFromConstraints(constraints, conference = false) {
851
- const video = constraints && constraints.video;
852
- const { constraints: newConstraints, } = this.getMediaConfiguration(video, conference, constraints);
853
- let newStream = null;
854
- try {
855
- newStream = await wazoMediaStreamFactory(newConstraints);
856
- }
857
- catch (e) { // Nothing to do when the user cancel the screensharing prompt
858
- }
859
- if (!newStream) {
860
- return null;
861
- }
862
- newStream.local = true;
863
- return newStream;
864
- }
865
756
  getHeldSession(sessionId) {
866
757
  return this.heldSessions[sessionId];
867
758
  }
868
759
  isCallHeld(session) {
869
- return this.getSipSessionId(session) in this.heldSessions;
760
+ return getSipSessionId(session) in this.heldSessions;
870
761
  }
871
762
  isVideoRemotelyHeld(sessionId) {
872
- const pc = this.getPeerConnection(sessionId);
873
- const sdp = pc && pc.remoteDescription ? pc.remoteDescription.sdp : null;
874
- if (!sdp) {
875
- return false;
876
- }
877
- const videoDirection = getVideoDirection(sdp);
878
- return videoDirection === 'sendonly';
763
+ return isVideoRemotelyHeld(this.heldSessions[sessionId]);
879
764
  }
880
765
  sendDTMF(session, tone) {
881
766
  if (!session.sessionDescriptionHandler) {
882
767
  return false;
883
768
  }
884
769
  logger.info('Sending DTMF', {
885
- id: this.getSipSessionId(session),
770
+ id: getSipSessionId(session),
886
771
  tone,
887
772
  });
888
773
  return session.sessionDescriptionHandler.sendDtmf(tone);
889
774
  }
890
775
  message(destination, message) {
891
- const uri = this._makeURI(destination);
776
+ const uri = makeURI(destination, this.config.host);
892
777
  if (!this.userAgent || !uri) {
893
778
  logger.warn('Null value on message', { uri, userAgent: this.userAgent });
894
779
  return;
@@ -899,7 +784,7 @@ export default class WebRTCClient extends Emitter {
899
784
  transfer(session, target) {
900
785
  this.hold(session);
901
786
  logger.info('Transfering a session', {
902
- id: this.getSipSessionId(session),
787
+ id: getSipSessionId(session),
903
788
  target,
904
789
  });
905
790
  const options = {
@@ -910,7 +795,7 @@ export default class WebRTCClient extends Emitter {
910
795
  },
911
796
  };
912
797
  setTimeout(() => {
913
- const uri = this._makeURI(target);
798
+ const uri = makeURI(target, this.config.host);
914
799
  if (!uri) {
915
800
  logger.warn('transfer timeout: null URI');
916
801
  return;
@@ -922,21 +807,21 @@ export default class WebRTCClient extends Emitter {
922
807
  atxfer(session) {
923
808
  this.hold(session);
924
809
  logger.info('webrtc transfer started', {
925
- id: this.getSipSessionId(session),
810
+ id: getSipSessionId(session),
926
811
  });
927
812
  const result = {
928
813
  newSession: null,
929
814
  init: async (target) => {
930
815
  logger.info('webrtc transfer initialized', {
931
- id: this.getSipSessionId(session),
816
+ id: getSipSessionId(session),
932
817
  target,
933
818
  });
934
819
  result.newSession = await this.call(target);
935
820
  },
936
821
  complete: () => {
937
822
  logger.info('webrtc transfer completed', {
938
- id: this.getSipSessionId(session),
939
- referId: this.getSipSessionId(result.newSession),
823
+ id: getSipSessionId(session),
824
+ referId: getSipSessionId(result.newSession),
940
825
  });
941
826
  session.refer(result.newSession);
942
827
  },
@@ -977,7 +862,7 @@ export default class WebRTCClient extends Emitter {
977
862
  return;
978
863
  }
979
864
  const core = this.userAgent?.userAgentCore;
980
- const fromURI = this._makeURI(this.config.authorizationUser || '');
865
+ const fromURI = makeURI(this.config.authorizationUser || '', this.config.host);
981
866
  const toURI = new URI('sip', '', this.config.host);
982
867
  if (fromURI) {
983
868
  const message = core?.makeOutgoingRequestMessage('OPTIONS', toURI, fromURI, toURI, {});
@@ -995,9 +880,6 @@ export default class WebRTCClient extends Emitter {
995
880
  getContactIdentifier() {
996
881
  return this.userAgent ? `${this.userAgent.configuration.contactName}/${this.userAgent.contact.uri}` : null;
997
882
  }
998
- isFirefox() {
999
- return this._isWeb() && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
1000
- }
1001
883
  changeAudioOutputVolume(volume) {
1002
884
  logger.info('Changing audio output volume', {
1003
885
  volume,
@@ -1101,12 +983,12 @@ export default class WebRTCClient extends Emitter {
1101
983
  };
1102
984
  }
1103
985
  changeSessionVideoInputDevice(id, session) {
1104
- if (!this.sessionWantsToDoVideo(session)) {
986
+ if (!sessionWantsToDoVideo(session)) {
1105
987
  return Promise.resolve();
1106
988
  }
1107
989
  const sdh = session.sessionDescriptionHandler;
1108
990
  const pc = sdh.peerConnection;
1109
- const sessionId = this.getSipSessionId(session);
991
+ const sessionId = getSipSessionId(session);
1110
992
  const localStream = this.getLocalStream(sessionId);
1111
993
  logger.info('changing video input device', {
1112
994
  id,
@@ -1143,7 +1025,7 @@ export default class WebRTCClient extends Emitter {
1143
1025
  }
1144
1026
  // let's update the local stream
1145
1027
  this.eventEmitter.emit('onVideoInputChange', stream);
1146
- this.setLocalMediaStream(sessionId, stream);
1028
+ setLocalMediaStream(this.sipSessions[sessionId], stream);
1147
1029
  return stream;
1148
1030
  });
1149
1031
  }
@@ -1162,12 +1044,12 @@ export default class WebRTCClient extends Emitter {
1162
1044
  return Promise.resolve();
1163
1045
  }
1164
1046
  const wasMuted = this.isAudioMuted(sipSession);
1165
- const shouldDoVideo = newConstraints ? newConstraints.video : this.sessionWantsToDoVideo(sipSession);
1047
+ const shouldDoVideo = newConstraints ? newConstraints.video : sessionWantsToDoVideo(sipSession);
1166
1048
  const shouldDoScreenSharing = newConstraints && newConstraints.screen;
1167
1049
  const desktop = newConstraints && newConstraints.desktop;
1168
1050
  logger.info('Sending reinvite', {
1169
1051
  clientId: this.clientId,
1170
- id: this.getSipSessionId(sipSession),
1052
+ id: getSipSessionId(sipSession),
1171
1053
  newConstraints,
1172
1054
  conference,
1173
1055
  audioOnly,
@@ -1185,7 +1067,7 @@ export default class WebRTCClient extends Emitter {
1185
1067
  conference,
1186
1068
  audioOnly,
1187
1069
  };
1188
- const { constraints, } = this.getMediaConfiguration(shouldDoVideo, conference, newConstraints);
1070
+ const { constraints, } = getMediaConfiguration(shouldDoVideo, conference, newConstraints);
1189
1071
  return sipSession.invite({
1190
1072
  requestDelegate: {
1191
1073
  onAccept: (response) => {
@@ -1201,11 +1083,11 @@ export default class WebRTCClient extends Emitter {
1201
1083
  sipSession.incomingInviteRequest.message.body = response.message.body;
1202
1084
  }
1203
1085
  logger.info('on re-INVITE accepted', {
1204
- id: this.getSipSessionId(sipSession),
1086
+ id: getSipSessionId(sipSession),
1205
1087
  wasMuted,
1206
1088
  shouldDoScreenSharing,
1207
1089
  });
1208
- this.updateRemoteStream(this.getSipSessionId(sipSession), false);
1090
+ updateRemoteStream(sipSession, false);
1209
1091
  if (wasMuted) {
1210
1092
  this.mute(sipSession);
1211
1093
  }
@@ -1238,103 +1120,55 @@ export default class WebRTCClient extends Emitter {
1238
1120
  return navigator.mediaDevices.getUserMedia(newConstraints);
1239
1121
  }
1240
1122
  getPeerConnection(sessionId) {
1241
- const sipSession = this.sipSessions[sessionId];
1242
- if (!sipSession) {
1243
- return null;
1244
- }
1245
- return sipSession.sessionDescriptionHandler ? sipSession.sessionDescriptionHandler.peerConnection : null;
1123
+ return getPeerConnection(this.sipSessions[sessionId]);
1246
1124
  }
1247
1125
  // Local streams
1248
1126
  getLocalStream(sessionId) {
1249
- const sipSession = this.sipSessions[sessionId];
1250
- return sipSession?.sessionDescriptionHandler?.localMediaStream || null;
1127
+ return getLocalStream(this.sipSessions[sessionId]);
1251
1128
  }
1252
1129
  getLocalTracks(sessionId) {
1253
- const localStream = this.getLocalStream(sessionId);
1254
- if (!localStream) {
1255
- return [];
1256
- }
1257
- return localStream.getTracks();
1130
+ return getLocalTracks(this.sipSessions[sessionId]);
1258
1131
  }
1259
1132
  hasLocalVideo(sessionId) {
1260
- return this.getLocalTracks(sessionId).some(this._isVideoTrack);
1133
+ return hasLocalVideo(this.sipSessions[sessionId]);
1261
1134
  }
1262
1135
  // Check if we have at least one video track, even muted or not live
1263
1136
  hasALocalVideoTrack(sessionId) {
1264
- return this.getLocalTracks(sessionId).some(track => track.kind === 'video');
1137
+ return hasALocalVideoTrack(this.sipSessions[sessionId]);
1265
1138
  }
1266
1139
  getLocalVideoStream(sessionId) {
1267
- return this.hasLocalVideo(sessionId) ? this.getLocalStream(sessionId) : null;
1140
+ return getLocalVideoStream(this.sipSessions[sessionId]);
1268
1141
  }
1269
1142
  // Remote streams
1270
1143
  getRemoteStream(sessionId) {
1271
- const sipSession = this.sipSessions[sessionId];
1272
- return sipSession?.sessionDescriptionHandler?.remoteMediaStream || null;
1144
+ return getRemoteStream(this.sipSessions[sessionId]);
1273
1145
  }
1274
1146
  getRemoteTracks(sessionId) {
1275
- const remoteStream = this.getRemoteStream(sessionId);
1276
- if (!remoteStream) {
1277
- return [];
1278
- }
1279
- return remoteStream.getTracks();
1147
+ return getRemoteTracks(this.sipSessions[sessionId]);
1280
1148
  }
1281
1149
  hasRemoteVideo(sessionId) {
1282
- return this.getRemoteTracks(sessionId).some(this._isVideoTrack);
1150
+ return hasRemoteVideo(this.sipSessions[sessionId]);
1283
1151
  }
1284
1152
  // Check if we have at least one video track, even muted or not live
1285
1153
  hasARemoteVideoTrack(sessionId) {
1286
- return this.getRemoteTracks(sessionId).some(track => track.kind === 'video');
1154
+ return hasARemoteVideoTrack(this.sipSessions[sessionId]);
1287
1155
  }
1288
1156
  getRemoteVideoStream(sessionId) {
1289
- if (this.isVideoRemotelyHeld(sessionId)) {
1290
- return null;
1291
- }
1292
- return this.hasRemoteVideo(sessionId) ? this.getRemoteStream(sessionId) : null;
1157
+ return getRemoteVideoStream(this.sipSessions[sessionId]);
1293
1158
  }
1294
1159
  // Useful in a react-native environment when remoteMediaStream is not updated
1295
1160
  getRemoteVideoStreamFromPc(sessionId) {
1296
- const pc = this.getPeerConnection(sessionId);
1297
- if (!pc) {
1298
- return null;
1299
- }
1300
- return pc.getRemoteStreams().find((stream) => !!stream.getVideoTracks().length);
1161
+ return getRemoteVideoStreamFromPc(this.sipSessions[sessionId]);
1301
1162
  }
1302
1163
  hasVideo(sessionId) {
1303
- return this.hasLocalVideo(sessionId) || this.hasRemoteVideo(sessionId);
1164
+ return hasVideoTrack(this.sipSessions[sessionId]);
1304
1165
  }
1305
1166
  hasAVideoTrack(sessionId) {
1306
- return this.hasALocalVideoTrack(sessionId) || this.hasARemoteVideoTrack(sessionId);
1307
- }
1308
- getSipSessionId(sipSession) {
1309
- if (!sipSession) {
1310
- return '';
1311
- }
1312
- const message = sipSession.message;
1313
- if (message?.callId) {
1314
- return message.callId.substring(0, SIP_ID_LENGTH);
1315
- }
1316
- // For Inviter
1317
- // @ts-ignore: private
1318
- if (sipSession instanceof Inviter && sipSession.outgoingRequestMessage) {
1319
- // @ts-ignore: private
1320
- return sipSession.outgoingRequestMessage.callId.substring(0, SIP_ID_LENGTH);
1321
- }
1322
- // For Invitation
1323
- return (sipSession.id || '').substring(0, SIP_ID_LENGTH);
1167
+ return hasAVideoTrack(this.sipSessions[sessionId]);
1324
1168
  }
1325
1169
  async waitForRegister() {
1326
1170
  return new Promise(resolve => this.on(REGISTERED, resolve));
1327
1171
  }
1328
- // eslint-disable-next-line no-unused-vars
1329
- sessionWantsToDoVideo(session) {
1330
- if (!session) {
1331
- return false;
1332
- }
1333
- const { body, } = session.request || session;
1334
- // Sometimes with InviteClientContext the body is in the body attribute ...
1335
- const sdp = typeof body === 'object' && body ? body.body : body;
1336
- return hasAnActiveVideo(sdp);
1337
- }
1338
1172
  hasHeartbeat() {
1339
1173
  return this.heartbeat.hasHeartbeat;
1340
1174
  }
@@ -1360,7 +1194,7 @@ export default class WebRTCClient extends Emitter {
1360
1194
  }
1361
1195
  onCallEnded(session) {
1362
1196
  this._cleanupMedia(session);
1363
- delete this.sipSessions[this.getSipSessionId(session)];
1197
+ delete this.sipSessions[getSipSessionId(session)];
1364
1198
  this._stopSendingStats(session);
1365
1199
  this.stopNetworkMonitoring(session);
1366
1200
  }
@@ -1373,7 +1207,7 @@ export default class WebRTCClient extends Emitter {
1373
1207
  this.userAgent.attemptReconnection();
1374
1208
  }
1375
1209
  storeSipSession(session) {
1376
- const id = this.getSipSessionId(session);
1210
+ const id = getSipSessionId(session);
1377
1211
  logger.info('storing sip session', { id, clientId: this.clientId });
1378
1212
  this.sipSessions[id] = session;
1379
1213
  }
@@ -1383,21 +1217,6 @@ export default class WebRTCClient extends Emitter {
1383
1217
  getSipSessionIds() {
1384
1218
  return Object.keys(this.sipSessions);
1385
1219
  }
1386
- setLocalMediaStream(sipSessionId, newStream) {
1387
- const sipSession = this.sipSessions[sipSessionId];
1388
- if (!sipSession) {
1389
- return;
1390
- }
1391
- logger.info('setting local media stream', {
1392
- sipSessionId,
1393
- tracks: newStream ? newStream.getTracks() : null,
1394
- });
1395
- if (sipSession.sessionDescriptionHandler) {
1396
- // eslint-disable-next-line no-underscore-dangle
1397
- // @ts-ignore: protected
1398
- sipSession.sessionDescriptionHandler._localMediaStream = newStream;
1399
- }
1400
- }
1401
1220
  async updateLocalStream(sipSessionId, newStream) {
1402
1221
  const sipSession = this.sipSessions[sipSessionId];
1403
1222
  if (!sipSession) {
@@ -1410,76 +1229,12 @@ export default class WebRTCClient extends Emitter {
1410
1229
  tracks: newStream.getTracks(),
1411
1230
  });
1412
1231
  if (oldStream) {
1413
- this._cleanupStream(oldStream);
1232
+ cleanupStream(oldStream);
1414
1233
  }
1415
- this.setLocalMediaStream(sipSessionId, newStream);
1234
+ setLocalMediaStream(sipSession, newStream);
1416
1235
  // Update the local video element
1417
1236
  await this._setupMedias(sipSession, newStream);
1418
1237
  }
1419
- updateRemoteStream(sessionId, allTracks = true) {
1420
- const remoteStream = this.getRemoteStream(sessionId);
1421
- const pc = this.getPeerConnection(sessionId);
1422
- logger.info('Updating remote stream', {
1423
- sessionId,
1424
- tracks: remoteStream ? remoteStream.getTracks() : null,
1425
- receiverTracks: pc && pc.getReceivers ? pc.getReceivers().map((receiver) => receiver.track) : null,
1426
- });
1427
- if (!pc || !remoteStream) {
1428
- return;
1429
- }
1430
- remoteStream.getTracks().forEach(track => {
1431
- if (allTracks || track.kind === 'video') {
1432
- remoteStream.removeTrack(track);
1433
- }
1434
- });
1435
- if (pc.getReceivers) {
1436
- pc.getReceivers().forEach((receiver) => {
1437
- if (allTracks || receiver.track.kind === 'video') {
1438
- remoteStream.addTrack(receiver.track);
1439
- }
1440
- });
1441
- }
1442
- }
1443
- getMediaConfiguration(enableVideo, conference = false, constraints = null) {
1444
- const screenSharing = constraints && 'screen' in constraints ? constraints.screen : false;
1445
- const isDesktop = constraints && 'desktop' in constraints ? constraints.desktop : false;
1446
- const withAudio = constraints && 'audio' in constraints ? constraints.audio : true;
1447
- const mandatoryVideo = constraints && typeof constraints.video === 'object' ? constraints.video.mandatory : {};
1448
- logger.info('Retrieving media a configuration', {
1449
- enableVideo,
1450
- screenSharing,
1451
- isDesktop,
1452
- withAudio,
1453
- constraints,
1454
- });
1455
- return {
1456
- constraints: {
1457
- // Exact constraint are not supported with `getDisplayMedia` and we must have a video=false in desktop screenshare
1458
- audio: screenSharing ? !isDesktop : withAudio ? this._getAudioConstraints() : false,
1459
- video: screenSharing ? isDesktop ? {
1460
- mandatory: {
1461
- chromeMediaSource: 'desktop',
1462
- ...(mandatoryVideo || {}),
1463
- },
1464
- } : {
1465
- cursor: 'always',
1466
- } : this._getVideoConstraints(enableVideo),
1467
- screen: screenSharing,
1468
- desktop: isDesktop,
1469
- conference,
1470
- },
1471
- enableVideo,
1472
- conference,
1473
- offerOptions: {
1474
- OfferToReceiveAudio: this._hasAudio(),
1475
- OfferToReceiveVideo: enableVideo,
1476
- mandatory: {
1477
- OfferToReceiveAudio: this._hasAudio(),
1478
- OfferToReceiveVideo: enableVideo,
1479
- },
1480
- },
1481
- };
1482
- }
1483
1238
  isConference(sessionId) {
1484
1239
  return sessionId in this.conferences;
1485
1240
  }
@@ -1555,25 +1310,14 @@ export default class WebRTCClient extends Emitter {
1555
1310
  this._onTransportError();
1556
1311
  }
1557
1312
  }
1558
- _isWeb() {
1559
- return typeof window === 'object' && typeof document === 'object';
1560
- }
1561
- _isVideoTrack(track) {
1562
- return track.kind === 'video' && track.readyState === 'live';
1563
- }
1564
1313
  _hasAudio() {
1565
1314
  return this.hasAudio;
1566
1315
  }
1567
1316
  _getAudioConstraints() {
1568
- // @ts-ignore: media constraints
1569
- return this.audio?.deviceId?.exact ? this.audio : true;
1317
+ return getAudioConstraints(this.audio);
1570
1318
  }
1571
1319
  _getVideoConstraints(video = false) {
1572
- if (!video) {
1573
- return false;
1574
- }
1575
- // @ts-ignore: media constraints
1576
- return this.video?.deviceId?.exact ? this.video : true;
1320
+ return getVideoConstraints(video && this.video);
1577
1321
  }
1578
1322
  _connectIfNeeded() {
1579
1323
  logger.info('connect if needed, checking', { connected: this.isConnected(), clientId: this.clientId });
@@ -1623,97 +1367,16 @@ export default class WebRTCClient extends Emitter {
1623
1367
  ...config,
1624
1368
  }));
1625
1369
  }
1626
- _createUaOptions(uaOptionsOverrides = {}) {
1627
- let { host, } = this.config;
1628
- let port = this.config.port || 443;
1629
- if (this.config.websocketSip) {
1630
- const webSocketSip = this.config.websocketSip.split(':');
1631
- [host] = webSocketSip;
1632
- port = Number(webSocketSip[1]);
1633
- }
1634
- const { userUuid } = this.config;
1635
- const uaOptions = {
1636
- noAnswerTimeout: NO_ANSWER_TIMEOUT,
1637
- authorizationUsername: this.config.authorizationUser,
1638
- authorizationPassword: this.config.password,
1639
- displayName: this.config.displayName,
1640
- hackIpInContact: true,
1641
- contactParams: {
1642
- transport: 'wss',
1643
- },
1644
- logBuiltinEnabled: this.config.log ? this.config.log.builtinEnabled : null,
1645
- logLevel: this.config.log ? this.config.log.logLevel : null,
1646
- logConnector: this.config.log ? this.config.log.connector : null,
1647
- uri: this._makeURI(this.config.authorizationUser || ''),
1648
- userAgentString: this.config.userAgentString || 'wazo-sdk',
1649
- reconnectionAttempts: 10000,
1650
- reconnectionDelay: 5,
1651
- sessionDescriptionHandlerFactory: (session, options = {}) => {
1652
- const uaLogger = session.userAgent.getLogger('sip.WazoSessionDescriptionHandler');
1653
- const isWeb = this._isWeb();
1654
- const iceGatheringTimeout = 'peerConnectionOptions' in options ? options.peerConnectionOptions.iceGatheringTimeout || DEFAULT_ICE_TIMEOUT : DEFAULT_ICE_TIMEOUT;
1655
- const sdhOptions = { ...options,
1656
- iceGatheringTimeout,
1657
- peerConnectionConfiguration: { ...defaultPeerConnectionConfiguration(),
1658
- ...(options.peerConnectionConfiguration || {}),
1659
- },
1660
- };
1661
- return new WazoSessionDescriptionHandler(uaLogger, wazoMediaStreamFactory, sdhOptions, isWeb, session);
1662
- },
1663
- transportOptions: {
1664
- traceSip: uaOptionsOverrides?.traceSip || false,
1665
- wsServers: `wss://${host}:${port}/api/asterisk/ws${userUuid ? `?userUuid=${userUuid}` : ''}`,
1666
- },
1667
- sessionDescriptionHandlerFactoryOptions: {
1668
- modifiers: [replaceLocalIpModifier],
1669
- alwaysAcquireMediaFirst: this.isFirefox(),
1670
- constraints: {
1671
- audio: this._getAudioConstraints(),
1672
- video: this._getVideoConstraints(),
1673
- },
1674
- peerConnectionOptions: {
1675
- iceCheckingTimeout: this.config.iceCheckingTimeout || 1000,
1676
- iceGatheringTimeout: this.config.iceCheckingTimeout || 1000,
1677
- rtcConfiguration: {
1678
- rtcpMuxPolicy: 'require',
1679
- iceServers: WebRTCClient.getIceServers(this.config.host),
1680
- ...this._getRtcOptions(),
1681
- ...(uaOptionsOverrides?.peerConnectionOptions || {}),
1682
- },
1683
- },
1684
- // Configuration used in SDH to create the PeerConnection
1685
- peerConnectionConfiguration: {
1686
- rtcpMuxPolicy: 'require',
1687
- iceServers: WebRTCClient.getIceServers(this.config.host),
1688
- ...(uaOptionsOverrides?.peerConnectionOptions || {}),
1689
- },
1690
- },
1691
- };
1692
- delete uaOptionsOverrides?.traceSip;
1693
- return {
1694
- ...uaOptions,
1695
- ...uaOptionsOverrides,
1696
- };
1697
- }
1698
- // eslint-disable-next-line no-unused-vars
1699
- _getRtcOptions() {
1700
- return {
1701
- mandatory: {
1702
- OfferToReceiveAudio: this._hasAudio(),
1703
- OfferToReceiveVideo: false,
1704
- },
1705
- };
1706
- }
1707
1370
  // Invitation and Inviter extends Session
1708
1371
  _setupSession(session) {
1709
- const sipSessionId = this.getSipSessionId(session);
1372
+ const sipSessionId = getSipSessionId(session);
1710
1373
  // When receiving an Invitation, the delegate is not defined.
1711
1374
  if (!session.delegate) {
1712
1375
  session.delegate = {};
1713
1376
  }
1714
1377
  session.delegate.onSessionDescriptionHandler = (sdh) => {
1715
1378
  sdh.on('error', (e) => {
1716
- this.eventEmitter.emit(ON_ERROR, e);
1379
+ this.eventEmitter.emit(ON_ERROR, e, session);
1717
1380
  });
1718
1381
  sdh.peerConnectionDelegate = {
1719
1382
  onicecandidateerror: (error) => {
@@ -1762,15 +1425,15 @@ export default class WebRTCClient extends Emitter {
1762
1425
  // @ts-ignore: private
1763
1426
  session.outgoingInviteRequest.message.body.body = request.body;
1764
1427
  }
1765
- this.updateRemoteStream(sipSessionId, false);
1428
+ updateRemoteStream(this.sipSessions[sipSessionId], false);
1766
1429
  await this._setupMedias(session);
1767
1430
  return this.eventEmitter.emit(ON_REINVITE, session, request, updatedCalleeName, updatedNumber, hadRemoteVideo);
1768
1431
  };
1769
1432
  }
1770
1433
  async _onProgress(session, early = false) {
1771
1434
  await this._setupMedias(session);
1772
- const sessionId = this.getSipSessionId(session).substr(0, 20);
1773
- this.updateRemoteStream(sessionId);
1435
+ const sessionId = getSipSessionId(session).substr(0, 20);
1436
+ updateRemoteStream(session);
1774
1437
  logger.info('progress received', {
1775
1438
  sessionId,
1776
1439
  early,
@@ -1785,21 +1448,14 @@ export default class WebRTCClient extends Emitter {
1785
1448
  });
1786
1449
  this.storeSipSession(session);
1787
1450
  await this._setupMedias(session);
1788
- this.updateRemoteStream(this.getSipSessionId(session), initAllTracks);
1789
- const pc = session.sessionDescriptionHandler?.peerConnection;
1451
+ updateRemoteStream(session, initAllTracks);
1452
+ const pc = getPeerConnection(session);
1790
1453
  const onTrack = (event) => {
1791
- const isAudioOnly = this._isAudioOnly(session);
1792
- const { kind, label, readyState, id, muted, } = event.track;
1793
- logger.info('on track event', {
1794
- isAudioOnly,
1795
- kind,
1796
- label,
1797
- readyState,
1798
- id,
1799
- muted,
1800
- });
1454
+ const audioOnly = isAudioOnly(session);
1455
+ const { kind, label, readyState, id, muted } = event.track;
1456
+ logger.info('on track event', { audioOnly, kind, label, readyState, id, muted });
1801
1457
  // Stop video track in audio only mode
1802
- if (isAudioOnly && kind === 'video') {
1458
+ if (audioOnly && kind === 'video') {
1803
1459
  event.track.stop();
1804
1460
  }
1805
1461
  this.eventEmitter.emit(ON_TRACK, session, event);
@@ -1824,16 +1480,13 @@ export default class WebRTCClient extends Emitter {
1824
1480
  }
1825
1481
  this._startSendingStats(session);
1826
1482
  }
1827
- _isAudioOnly(session) {
1828
- return Boolean(session.sessionDescriptionHandlerModifiersReInvite.find(modifier => modifier === stripVideo));
1829
- }
1830
1483
  async _setupMedias(session, newStream = null) {
1831
- if (!this._isWeb()) {
1484
+ if (!isWeb()) {
1832
1485
  logger.info('Setup media on mobile, no need to setup html element, bailing');
1833
1486
  return;
1834
1487
  }
1835
1488
  // Safari hack, because you cannot call .play() from a non user action
1836
- const sessionId = this.getSipSessionId(session);
1489
+ const sessionId = getSipSessionId(session);
1837
1490
  const isConference = this.isConference(sessionId);
1838
1491
  if (sessionId in this.audioElements) {
1839
1492
  logger.info('html element already exists for session', {
@@ -1841,7 +1494,7 @@ export default class WebRTCClient extends Emitter {
1841
1494
  });
1842
1495
  return;
1843
1496
  }
1844
- if (this._hasAudio() && this._isWeb() && !(sessionId in this.audioElements)) {
1497
+ if (this._hasAudio() && isWeb() && !(sessionId in this.audioElements)) {
1845
1498
  await this.createAudioElementFor(sessionId);
1846
1499
  }
1847
1500
  const audioElement = this.audioElements[sessionId];
@@ -1878,10 +1531,10 @@ export default class WebRTCClient extends Emitter {
1878
1531
  audioElement.play().catch(() => { });
1879
1532
  }
1880
1533
  _cleanupMedia(session) {
1881
- const sessionId = this.getSipSessionId(session);
1534
+ const sessionId = getSipSessionId(session);
1882
1535
  const localStream = this.getLocalStream(sessionId);
1883
1536
  if (localStream) {
1884
- this._cleanupStream(localStream);
1537
+ cleanupStream(localStream);
1885
1538
  }
1886
1539
  const cleanLocalElement = (id) => {
1887
1540
  const element = this.audioElements[id];
@@ -1895,80 +1548,15 @@ export default class WebRTCClient extends Emitter {
1895
1548
  element.srcObject = null;
1896
1549
  delete this.audioElements[id];
1897
1550
  };
1898
- if (this._hasAudio() && this._isWeb()) {
1551
+ if (this._hasAudio() && isWeb()) {
1899
1552
  if (session) {
1900
- cleanLocalElement(this.getSipSessionId(session));
1553
+ cleanLocalElement(getSipSessionId(session));
1901
1554
  }
1902
1555
  else {
1903
1556
  Object.keys(this.audioElements).forEach(id => cleanLocalElement(id));
1904
1557
  }
1905
1558
  }
1906
1559
  }
1907
- _cleanupStream(stream) {
1908
- stream.getTracks().filter(track => track.enabled).forEach(track => track.stop());
1909
- }
1910
- _toggleAudio(session, muteAudio) {
1911
- const sdh = session.sessionDescriptionHandler;
1912
- const pc = (sdh?.peerConnection || null);
1913
- if (!pc) {
1914
- return;
1915
- }
1916
- if (pc.getSenders) {
1917
- pc.getSenders().forEach((sender) => {
1918
- if (sender && sender.track && sender.track.kind === 'audio') {
1919
- // eslint-disable-next-line
1920
- sender.track.enabled = !muteAudio;
1921
- }
1922
- });
1923
- }
1924
- else {
1925
- pc.getLocalStreams().forEach((stream) => {
1926
- stream.getAudioTracks().forEach(track => {
1927
- // eslint-disable-next-line
1928
- track.enabled = !muteAudio;
1929
- });
1930
- });
1931
- }
1932
- }
1933
- _toggleVideo(session, muteCamera) {
1934
- const sdh = session.sessionDescriptionHandler;
1935
- const pc = sdh?.peerConnection;
1936
- if (pc?.getSenders) {
1937
- pc.getSenders().forEach((sender) => {
1938
- if (sender && sender.track && sender.track.kind === 'video') {
1939
- // eslint-disable-next-line
1940
- sender.track.enabled = !muteCamera;
1941
- }
1942
- });
1943
- }
1944
- else {
1945
- pc.getLocalStreams().forEach((stream) => {
1946
- stream.getVideoTracks().forEach(track => {
1947
- // eslint-disable-next-line
1948
- track.enabled = !muteCamera;
1949
- });
1950
- });
1951
- }
1952
- }
1953
- /**
1954
- * @param pc RTCPeerConnection
1955
- */
1956
- _getRemoteStream(pc) {
1957
- let remoteStream = null;
1958
- if (pc && pc.getReceivers) {
1959
- remoteStream = typeof global !== 'undefined' && global.window && global.window.MediaStream ? new global.window.MediaStream() : new window.MediaStream();
1960
- pc.getReceivers().forEach((receiver) => {
1961
- const { track, } = receiver;
1962
- if (track && remoteStream) {
1963
- remoteStream.addTrack(track);
1964
- }
1965
- });
1966
- }
1967
- else if (pc) {
1968
- [remoteStream] = pc.getRemoteStreams();
1969
- }
1970
- return remoteStream;
1971
- }
1972
1560
  _cleanupRegister() {
1973
1561
  if (this.registerer) {
1974
1562
  // @ts-ignore: removeAllListeners does not exist
@@ -1982,7 +1570,7 @@ export default class WebRTCClient extends Emitter {
1982
1570
  if (!pc) {
1983
1571
  return;
1984
1572
  }
1985
- const sessionId = this.getSipSessionId(session);
1573
+ const sessionId = getSipSessionId(session);
1986
1574
  getStats(pc, (result) => {
1987
1575
  const { results, internal, nomore, ...stats } = result;
1988
1576
  this.statsIntervals[sessionId] = nomore;
@@ -1993,7 +1581,7 @@ export default class WebRTCClient extends Emitter {
1993
1581
  }, SEND_STATS_DELAY);
1994
1582
  }
1995
1583
  _stopSendingStats(session) {
1996
- const sessionId = this.getSipSessionId(session);
1584
+ const sessionId = getSipSessionId(session);
1997
1585
  logger.trace('Check for stopping stats', {
1998
1586
  sessionId,
1999
1587
  ids: Object.keys(this.statsIntervals),
@@ -2006,9 +1594,6 @@ export default class WebRTCClient extends Emitter {
2006
1594
  delete this.statsIntervals[sessionId];
2007
1595
  }
2008
1596
  }
2009
- _makeURI(target) {
2010
- return UserAgent.makeURI(`sip:${target}@${this.config.host}`);
2011
- }
2012
1597
  async _disconnectTransport(force = false) {
2013
1598
  const transport = this.userAgent?.transport;
2014
1599
  if (force && transport) {
@@ -2030,96 +1615,13 @@ export default class WebRTCClient extends Emitter {
2030
1615
  }
2031
1616
  }
2032
1617
  async _fetchNetworkStats(sessionId) {
2033
- const session = this.getSipSession(sessionId);
2034
- const stats = session ? await this.getStats(session) : null;
2035
- if (!stats || !(sessionId in this.sessionNetworkStats)) {
1618
+ const session = this.sipSessions[sessionId];
1619
+ if (!(sessionId in this.sessionNetworkStats)) {
2036
1620
  return Promise.resolve(null);
2037
1621
  }
2038
- const networkStats = {
2039
- audioBytesSent: 0,
2040
- videoBytesSent: 0,
2041
- videoBytesReceived: 0,
2042
- audioBytesReceived: 0,
2043
- };
2044
1622
  const nbStats = this.sessionNetworkStats[sessionId].length;
2045
1623
  const lastNetworkStats = this.sessionNetworkStats[sessionId][nbStats - 1];
2046
- const lastAudioSent = lastNetworkStats ? lastNetworkStats.audioBytesSent : 0;
2047
- const lastAudioContentSent = lastNetworkStats ? lastNetworkStats.audioContentSent : 0;
2048
- const lastVideoSent = lastNetworkStats ? lastNetworkStats.videoBytesSent : 0;
2049
- const lastAudioReceived = lastNetworkStats ? lastNetworkStats.audioBytesReceived : 0;
2050
- const lastAudioContentReceived = lastNetworkStats ? lastNetworkStats.audioContentReceived : 0;
2051
- const lastVideoReceived = lastNetworkStats ? lastNetworkStats.videoBytesReceived : 0;
2052
- const lastTransportSent = lastNetworkStats ? lastNetworkStats.transportSent : 0;
2053
- const lastTransportReceived = lastNetworkStats ? lastNetworkStats.transportReceived : 0;
2054
- let audioBytesSent = 0; // content + header
2055
- let audioBytesReceived = 0; // content + header
2056
- let audioContentSent = 0; // useful to detect blank call
2057
- let audioContentReceived = 0;
2058
- let videoBytesSent = 0;
2059
- let videoBytesReceived = 0;
2060
- let transportSent = 0;
2061
- let transportReceived = 0;
2062
- let packetsLost = 0;
2063
- stats.forEach(report => {
2064
- if (report.type === 'outbound-rtp' && report.kind === 'audio') {
2065
- audioBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
2066
- audioContentSent += Number(report.bytesSent);
2067
- }
2068
- if (report.type === 'remote-outbound-rtp' && report.kind === 'audio') {
2069
- audioBytesSent += Number(report.bytesSent);
2070
- }
2071
- if (report.type === 'outbound-rtp' && report.kind === 'video') {
2072
- videoBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
2073
- }
2074
- if (report.type === 'inbound-rtp' && report.kind === 'audio') {
2075
- packetsLost += Number(report.packetsLost);
2076
- networkStats.packetsReceived = Number(report.packetsReceived);
2077
- audioBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
2078
- audioContentReceived += Number(report.bytesReceived);
2079
- }
2080
- if (report.type === 'inbound-rtp' && report.kind === 'video') {
2081
- videoBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
2082
- }
2083
- if (report.type === 'outbound-rtp' && report.kind === 'video') {
2084
- if ('framesPerSecond' in report) {
2085
- networkStats.framesPerSecond = Number(report.framesPerSecond);
2086
- }
2087
- if ('framerateMean' in report) {
2088
- // framerateMean is only available in FF
2089
- networkStats.framesPerSecond = Math.round(report.framerateMean);
2090
- }
2091
- }
2092
- if (report.type === 'remote-inbound-rtp' && report.kind === 'audio') {
2093
- packetsLost += Number(report.packetsLost);
2094
- networkStats.roundTripTime = Number(report.roundTripTime);
2095
- networkStats.jitter = Number(report.jitter);
2096
- }
2097
- if (report.type === 'remote-inbound-rtp' && report.kind === 'video') {
2098
- packetsLost += Number(report.packetsLost);
2099
- }
2100
- if (report.type === 'transport') {
2101
- transportSent += Number(report.bytesSent);
2102
- transportReceived += Number(report.bytesReceived);
2103
- }
2104
- });
2105
- networkStats.packetsLost = packetsLost;
2106
- networkStats.totalAudioBytesSent = audioBytesSent;
2107
- networkStats.audioBytesSent = audioBytesSent - lastAudioSent;
2108
- networkStats.totalAudioBytesReceived = audioBytesReceived - lastAudioReceived;
2109
- networkStats.audioBytesReceived = audioBytesReceived;
2110
- networkStats.totalAudioContentSent = audioContentSent;
2111
- networkStats.audioContentSent = audioContentSent - lastAudioContentSent;
2112
- networkStats.totalAudioBytesReceived = audioContentReceived - lastAudioContentReceived;
2113
- networkStats.audioContentReceived = audioContentReceived;
2114
- networkStats.totalVideoBytesSent = videoBytesSent;
2115
- networkStats.videoBytesSent = videoBytesSent - lastVideoSent;
2116
- networkStats.totalVideoBytesReceived = videoBytesReceived;
2117
- networkStats.videoBytesReceived = videoBytesReceived - lastVideoReceived;
2118
- networkStats.totalTransportSent = transportSent - lastTransportSent;
2119
- networkStats.transportSent = transportSent;
2120
- networkStats.totalTransportReceived = transportReceived;
2121
- networkStats.transportReceived = transportReceived - lastTransportReceived;
2122
- networkStats.bandwidth = networkStats.audioBytesSent + networkStats.audioBytesReceived + networkStats.videoBytesSent + networkStats.videoBytesReceived + networkStats.transportReceived + networkStats.transportSent;
1624
+ const networkStats = await fetchNetworkStats(session, lastNetworkStats);
2123
1625
  if (this.sessionNetworkStats[sessionId]) {
2124
1626
  this.eventEmitter.emit(ON_NETWORK_STATS, session, networkStats, this.sessionNetworkStats[sessionId]);
2125
1627
  if (sessionId in this.sessionNetworkStats) {