@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,6 +1,6 @@
1
1
  /* eslint-disable class-methods-use-this, no-param-reassign, max-classes-per-file, no-underscore-dangle */
2
2
 
3
- /* global window, document, navigator */
3
+ /* global document, navigator */
4
4
  import 'webrtc-adapter';
5
5
  import type { InviterInviteOptions } from 'sip.js/lib/api/inviter-invite-options';
6
6
  import type { InvitationRejectOptions } from 'sip.js/lib/api/invitation-reject-options';
@@ -9,8 +9,6 @@ import type { SessionByeOptions } from 'sip.js/lib/api/session-bye-options';
9
9
  import type { InvitationAcceptOptions } from 'sip.js/lib/api/invitation-accept-options';
10
10
  import type { SessionDialog } from 'sip.js/lib/core/dialogs/session-dialog';
11
11
  import type { IncomingRequestMessage } from 'sip.js/lib/core/messages/incoming-request-message';
12
- import type { SessionDescriptionHandlerFactoryOptions } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-factory-options';
13
- import type { SessionDescriptionHandlerConfiguration } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-configuration';
14
12
  import type { SessionDescriptionHandler } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler';
15
13
  import { UserAgentState } from 'sip.js/lib/api/user-agent-state';
16
14
  import { Parser } from 'sip.js/lib/core/messages/parser';
@@ -23,27 +21,18 @@ import { Messager } from 'sip.js/lib/api/messager';
23
21
  import { RegistererState } from 'sip.js/lib/api/registerer-state';
24
22
  import { SessionState } from 'sip.js/lib/api/session-state';
25
23
  import { TransportState } from 'sip.js/lib/api/transport-state';
26
- import { defaultPeerConnectionConfiguration } from 'sip.js/lib/platform/web/session-description-handler/peer-connection-configuration-default';
27
24
  import getStats from 'getstats';
28
25
 
29
26
  import { OutgoingByeRequest, OutgoingInviteRequest, OutgoingRequest } from 'sip.js/lib/core';
30
- import { Inviter, Invitation, Registerer, Session, SessionDescriptionHandlerOptions } from 'sip.js/lib/api';
31
- import WazoSessionDescriptionHandler, { wazoMediaStreamFactory } from './lib/WazoSessionDescriptionHandler';
27
+ import { Inviter, Invitation, Registerer, SessionDescriptionHandlerOptions } from 'sip.js/lib/api';
32
28
  import Emitter from './utils/Emitter';
33
29
  import ApiClient from './api-client';
34
30
  import IssueReporter from './service/IssueReporter';
35
31
  import Heartbeat from './utils/Heartbeat';
36
- import { getVideoDirection, hasAnActiveVideo } from './utils/sdp';
37
- import { lastIndexOf } from './utils/array';
38
- import type { MediaConfig, UserAgentConfigOverrides, WebRtcConfig, UserAgentOptions, IncomingResponse, PeerConnection, WazoSession, WazoTransport } from './domain/types';
39
-
40
- // We need to replace 0.0.0.0 to 127.0.0.1 in the sdp to avoid MOH during a createOffer.
41
- export const replaceLocalIpModifier = (description: Record<string, any>) => Promise.resolve({ // description is immutable... so we have to clone it or the `type` attribute won't be returned.
42
- ...JSON.parse(JSON.stringify(description)),
43
- sdp: description.sdp.replace('c=IN IP4 0.0.0.0', 'c=IN IP4 127.0.0.1'),
44
- });
45
- const SIP_ID_LENGTH = 36;
46
- const DEFAULT_ICE_TIMEOUT = 3000;
32
+ import { getSipSessionId, makeURI, replaceLocalIpModifier, SIP_ID_LENGTH } from './utils/sdp';
33
+ import type { MediaConfig, UserAgentConfigOverrides, WebRtcConfig, IncomingResponse, PeerConnection, WazoSession, WazoTransport } from './domain/types';
34
+ import { cleanupStream, createUaOptions, fetchNetworkStats, getAudioConstraints, getLocalStream, getLocalTracks, getLocalVideoStream, getMediaConfiguration, getPeerConnection, getRemoteStream, getRemoteTracks, getRemoteVideoStream, getRemoteVideoStreamFromPc, getStreamFromConstraints, getVideoConstraints, hasALocalVideoTrack, hasARemoteVideoTrack, hasAVideoTrack, hasLocalVideo, hasRemoteVideo, hasVideoTrack, isAudioOnly, isVideoRemotelyHeld, isWeb, sessionWantsToDoVideo, setLocalMediaStream, toggleAudio, toggleVideo, updateRemoteStream } from './utils/webrtc';
35
+
47
36
  const SEND_STATS_DELAY = 5000;
48
37
  const states = ['STATUS_NULL', 'STATUS_NEW', 'STATUS_CONNECTING', 'STATUS_CONNECTED', 'STATUS_COMPLETED'];
49
38
  const logger = IssueReporter ? IssueReporter.loggerFor('webrtc-client') : console;
@@ -67,13 +56,12 @@ const ON_ERROR = 'onError';
67
56
  const ON_SCREEN_SHARING_REINVITE = 'onScreenSharingReinvite';
68
57
  const ON_NETWORK_STATS = 'onNetworkStats';
69
58
  const ON_DISCONNECTED = 'onDisconnected';
59
+
70
60
  export const events = [REGISTERED, UNREGISTERED, REGISTRATION_FAILED, INVITE];
71
61
  export const transportEvents = [CONNECTED, DISCONNECTED, TRANSPORT_ERROR, MESSAGE];
72
62
  export class CanceledCallError extends Error {}
73
63
 
74
64
  const MAX_REGISTER_TRIES = 5;
75
- // setting a 24hr timeout and letting the backend define the actual value
76
- const NO_ANSWER_TIMEOUT = 60 * 60 * 24; // in seconds
77
65
 
78
66
  export default class WebRTCClient extends Emitter {
79
67
  clientId: number;
@@ -165,23 +153,6 @@ export default class WebRTCClient extends Emitter {
165
153
 
166
154
  ON_DISCONNECTED: string;
167
155
 
168
- static isAPrivateIp(ip: string): boolean {
169
- const regex = /^(?:10|127|172\.(?:1[6-9]|2[0-9]|3[01])|192\.168)\..*/;
170
- return regex.exec(ip) == null;
171
- }
172
-
173
- static getIceServers(ip: string): Array<{
174
- urls: Array<string>;
175
- }> {
176
- if (WebRTCClient.isAPrivateIp(ip)) {
177
- return [{
178
- urls: ['stun:stun.l.google.com:19302', 'stun:stun4.l.google.com:19302'],
179
- }];
180
- }
181
-
182
- return [];
183
- }
184
-
185
156
  constructor(config: WebRtcConfig, session?: WazoSession, uaConfigOverrides?: UserAgentConfigOverrides) {
186
157
  super();
187
158
 
@@ -254,7 +225,7 @@ export default class WebRTCClient extends Emitter {
254
225
  }
255
226
 
256
227
  createUserAgent(uaConfigOverrides?: UserAgentConfigOverrides): UserAgent {
257
- const uaOptions = this._createUaOptions(uaConfigOverrides);
228
+ const uaOptions = createUaOptions(this.config, uaConfigOverrides, this.audio, this.video);
258
229
 
259
230
  logger.info('sdk webrtc, creating UA', {
260
231
  uaOptions: { ...uaOptions, authorizationPassword: `${uaOptions?.authorizationPassword?.slice(0, 5)}xxxx` },
@@ -275,7 +246,7 @@ export default class WebRTCClient extends Emitter {
275
246
  this._setupSession(invitation);
276
247
 
277
248
  const shouldAutoAnswer = !!invitation.request.getHeader('alert-info');
278
- this.eventEmitter.emit(INVITE, invitation, this.sessionWantsToDoVideo(invitation), shouldAutoAnswer);
249
+ this.eventEmitter.emit(INVITE, invitation, sessionWantsToDoVideo(invitation), shouldAutoAnswer);
279
250
  },
280
251
  };
281
252
  const ua = new UserAgent(uaOptions);
@@ -386,7 +357,7 @@ export default class WebRTCClient extends Emitter {
386
357
  return Promise.resolve();
387
358
  }
388
359
 
389
- const registerOptions = this._isWeb() ? {} : {
360
+ const registerOptions = isWeb() ? {} : {
390
361
  extraContactHeaderParams: ['mobility=mobile'],
391
362
  };
392
363
 
@@ -572,7 +543,7 @@ export default class WebRTCClient extends Emitter {
572
543
  inviterOptions.sessionDescriptionHandlerModifiersReInvite = [stripVideo];
573
544
  }
574
545
 
575
- const uri = this._makeURI(number);
546
+ const uri = makeURI(number, this.config.host);
576
547
  let session: WazoSession | null = null;
577
548
 
578
549
  if (uri) {
@@ -588,7 +559,7 @@ export default class WebRTCClient extends Emitter {
588
559
  }
589
560
 
590
561
  if (conference) {
591
- this.conferences[this.getSipSessionId(session)] = true;
562
+ this.conferences[getSipSessionId(session)] = true;
592
563
  }
593
564
 
594
565
  const inviteOptions: InviterInviteOptions = {
@@ -615,7 +586,7 @@ export default class WebRTCClient extends Emitter {
615
586
  this.eventEmitter.emit(REJECTED, session, response);
616
587
  },
617
588
  },
618
- sessionDescriptionHandlerOptions: this.getMediaConfiguration(enableVideo || false, conference),
589
+ sessionDescriptionHandlerOptions: getMediaConfiguration(enableVideo || false, conference),
619
590
  };
620
591
 
621
592
  if (inviteOptions.sessionDescriptionHandlerOptions) {
@@ -652,7 +623,7 @@ export default class WebRTCClient extends Emitter {
652
623
  }
653
624
 
654
625
  const options = {
655
- sessionDescriptionHandlerOptions: this.getMediaConfiguration(enableVideo || false),
626
+ sessionDescriptionHandlerOptions: getMediaConfiguration(enableVideo || false),
656
627
  };
657
628
  return this._accept(session, options).then(() => {
658
629
  // @ts-ignore: private
@@ -683,7 +654,7 @@ export default class WebRTCClient extends Emitter {
683
654
 
684
655
  this._cleanupMedia(session);
685
656
 
686
- delete this.sipSessions[this.getSipSessionId(session)];
657
+ delete this.sipSessions[getSipSessionId(session)];
687
658
  // Check if Invitation or Inviter (Invitation = incoming call)
688
659
  const isInviter = session instanceof Inviter;
689
660
 
@@ -707,19 +678,9 @@ export default class WebRTCClient extends Emitter {
707
678
  return Promise.resolve(null);
708
679
  }
709
680
 
710
- async getStats(session: WazoSession): Promise<RTCStatsReport | null> {
711
- const pc = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.peerConnection as RTCPeerConnection;
712
-
713
- if (!pc) {
714
- return null;
715
- }
716
-
717
- return pc.getStats(null);
718
- }
719
-
720
681
  // Fetch and emit an event at `interval` with session network stats
721
682
  startNetworkMonitoring(session: WazoSession, interval = 1000): void {
722
- const sessionId = this.getSipSessionId(session);
683
+ const sessionId = getSipSessionId(session);
723
684
  logger.info('starting network inspection', {
724
685
  id: sessionId,
725
686
  clientId: this.clientId,
@@ -729,7 +690,7 @@ export default class WebRTCClient extends Emitter {
729
690
  }
730
691
 
731
692
  stopNetworkMonitoring(session: WazoSession): void {
732
- const sessionId = this.getSipSessionId(session);
693
+ const sessionId = getSipSessionId(session);
733
694
  const exists = (sessionId in this.networkMonitoringInterval);
734
695
  logger.info('stopping network inspection', {
735
696
  clientId: this.clientId,
@@ -808,21 +769,12 @@ export default class WebRTCClient extends Emitter {
808
769
  logger.info('sdk webrtc client closed', { clientId: this.clientId });
809
770
  }
810
771
 
811
- getNumber(session: Inviter): string | null | undefined {
812
- if (!session) {
813
- return null;
814
- }
815
-
816
- // @ts-ignore: private
817
- return session.remoteIdentity.uri._normal.user;
818
- }
819
-
820
772
  mute(session: WazoSession): void {
821
773
  logger.info('sdk webrtc mute', {
822
774
  id: session.id,
823
775
  });
824
776
 
825
- this._toggleAudio(session, true);
777
+ toggleAudio(session, true);
826
778
  }
827
779
 
828
780
  unmute(session: WazoSession): void {
@@ -830,7 +782,7 @@ export default class WebRTCClient extends Emitter {
830
782
  id: session.id,
831
783
  });
832
784
 
833
- this._toggleAudio(session, false);
785
+ toggleAudio(session, false);
834
786
  }
835
787
 
836
788
  isAudioMuted(session: WazoSession): boolean {
@@ -875,7 +827,7 @@ export default class WebRTCClient extends Emitter {
875
827
  id: session.id,
876
828
  });
877
829
 
878
- this._toggleVideo(session, false);
830
+ toggleVideo(session, false);
879
831
  }
880
832
 
881
833
  toggleCameraOff(session: WazoSession): void {
@@ -883,11 +835,11 @@ export default class WebRTCClient extends Emitter {
883
835
  id: session.id,
884
836
  });
885
837
 
886
- this._toggleVideo(session, true);
838
+ toggleVideo(session, true);
887
839
  }
888
840
 
889
841
  hold(session: WazoSession, isConference = false, hadVideo = false): Promise<OutgoingInviteRequest | void> {
890
- const sessionId = this.getSipSessionId(session);
842
+ const sessionId = getSipSessionId(session);
891
843
  const hasVideo = hadVideo || this.hasLocalVideo(sessionId);
892
844
  logger.info('sdk webrtc hold', {
893
845
  sessionId,
@@ -920,9 +872,9 @@ export default class WebRTCClient extends Emitter {
920
872
  hold: true,
921
873
  conference: isConference,
922
874
  } as SessionDescriptionHandlerOptions;
923
- const options = this.getMediaConfiguration(false, isConference);
875
+ const options = getMediaConfiguration(false, isConference);
924
876
 
925
- if (!this._isWeb()) {
877
+ if (!isWeb()) {
926
878
  options.sessionDescriptionHandlerModifiers = [holdModifier];
927
879
  }
928
880
 
@@ -945,7 +897,7 @@ export default class WebRTCClient extends Emitter {
945
897
  }
946
898
 
947
899
  unhold(session: WazoSession, isConference = false): Promise<OutgoingInviteRequest | void> {
948
- const sessionId = this.getSipSessionId(session);
900
+ const sessionId = getSipSessionId(session);
949
901
  const hasVideo = sessionId in this.heldSessions && this.heldSessions[sessionId].hasVideo;
950
902
  logger.info('sdk webrtc unhold', {
951
903
  sessionId,
@@ -963,14 +915,14 @@ export default class WebRTCClient extends Emitter {
963
915
 
964
916
  this.unmute(session);
965
917
 
966
- delete this.heldSessions[this.getSipSessionId(session)];
918
+ delete this.heldSessions[getSipSessionId(session)];
967
919
  session.sessionDescriptionHandlerOptionsReInvite = {
968
920
  hold: false,
969
921
  conference: isConference,
970
922
  } as SessionDescriptionHandlerOptions;
971
- const options = this.getMediaConfiguration(false, isConference);
923
+ const options = getMediaConfiguration(false, isConference);
972
924
 
973
- if (!this._isWeb()) {
925
+ if (!isWeb()) {
974
926
  // We should sent an empty `sessionDescriptionHandlerModifiers` or sip.js will take the last sent modifiers
975
927
  // (eg: holdModifier)
976
928
  options.sessionDescriptionHandlerModifiers = [];
@@ -987,113 +939,16 @@ export default class WebRTCClient extends Emitter {
987
939
  });
988
940
  }
989
941
 
990
- // Returns true if a re-INVITE is required
991
- async upgradeToVideo(session: WazoSession, constraints: Record<string, any>, isConference: boolean): Promise<MediaStream | undefined> {
992
- const pc = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.peerConnection;
993
- // Check if a video sender already exists
994
- let videoSender;
995
-
996
- if (isConference) {
997
- // We search for the last transceiver without `video-` in the mid (video- means remote transceiver)
998
- const transceivers = pc?.getTransceivers() || [];
999
- const transceiverIdx = lastIndexOf(transceivers, transceiver => transceiver.sender.track === null && transceiver.mid && transceiver.mid.indexOf('video') === -1);
1000
- videoSender = transceiverIdx !== -1 ? transceivers[transceiverIdx].sender : null;
1001
- } else {
1002
- videoSender = pc && pc.getSenders && pc.getSenders().find((sender) => sender.track === null);
1003
- }
1004
-
1005
- if (!videoSender) {
1006
- // When no video sender found, it means that we're in the first video upgrade in 1:1
1007
- return;
1008
- }
1009
-
1010
- // Reuse bidirectional video stream
1011
- const newStream = await this.getStreamFromConstraints(constraints);
1012
-
1013
- if (!newStream) {
1014
- console.warn(`Can't create media stream with: ${JSON.stringify(constraints || {})}`);
1015
- return;
1016
- }
1017
-
1018
- // Add previous local audio track
1019
- if (constraints && !constraints.audio) {
1020
- const localVideoStream: MediaStream = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.localMediaStream;
1021
- const localAudioTrack = localVideoStream.getTracks().find(track => track.kind === 'audio');
1022
-
1023
- if (localAudioTrack) {
1024
- newStream.addTrack(localAudioTrack);
1025
- }
1026
- }
1027
-
1028
- const videoTrack = newStream.getVideoTracks()[0];
1029
-
1030
- if (videoTrack) {
1031
- videoSender.replaceTrack(videoTrack);
1032
- }
1033
-
1034
- this.setLocalMediaStream(this.getSipSessionId(session), newStream);
1035
- return newStream;
1036
- }
1037
-
1038
- downgradeToAudio(session: WazoSession): void {
1039
- // Release local video stream when downgrading to audio
1040
- const sessionDescriptionHandler = session.sessionDescriptionHandler as SessionDescriptionHandler;
1041
- const localStream = sessionDescriptionHandler?.localMediaStream;
1042
- const pc = sessionDescriptionHandler?.peerConnection;
1043
- const videoTracks = localStream.getVideoTracks();
1044
-
1045
- // Remove video senders
1046
- if (pc?.getSenders) {
1047
- pc.getSenders().filter((sender) => sender.track && sender.track.kind === 'video').forEach((videoSender) => {
1048
- videoSender.replaceTrack(null);
1049
- });
1050
- }
1051
-
1052
- videoTracks.forEach((videoTrack: MediaStreamTrack) => {
1053
- videoTrack.enabled = false;
1054
- videoTrack.stop();
1055
- localStream.removeTrack(videoTrack);
1056
- });
1057
- }
1058
-
1059
- async getStreamFromConstraints(constraints: Record<string, any>, conference = false): Promise<MediaStream | null | undefined> {
1060
- const video = constraints && constraints.video;
1061
- const {
1062
- constraints: newConstraints,
1063
- } = this.getMediaConfiguration(video, conference, constraints);
1064
- let newStream: MediaStream & { local?: boolean } | null = null;
1065
-
1066
- try {
1067
- newStream = await wazoMediaStreamFactory(newConstraints);
1068
- } catch (e: any) { // Nothing to do when the user cancel the screensharing prompt
1069
- }
1070
-
1071
- if (!newStream) {
1072
- return null;
1073
- }
1074
-
1075
- newStream.local = true;
1076
- return newStream;
1077
- }
1078
-
1079
942
  getHeldSession(sessionId: string) {
1080
943
  return this.heldSessions[sessionId];
1081
944
  }
1082
945
 
1083
946
  isCallHeld(session: WazoSession): boolean {
1084
- return this.getSipSessionId(session) in this.heldSessions;
947
+ return getSipSessionId(session) in this.heldSessions;
1085
948
  }
1086
949
 
1087
950
  isVideoRemotelyHeld(sessionId: string): boolean {
1088
- const pc = this.getPeerConnection(sessionId);
1089
- const sdp = pc && pc.remoteDescription ? pc.remoteDescription.sdp : null;
1090
-
1091
- if (!sdp) {
1092
- return false;
1093
- }
1094
-
1095
- const videoDirection = getVideoDirection(sdp);
1096
- return videoDirection === 'sendonly';
951
+ return isVideoRemotelyHeld(this.heldSessions[sessionId]);
1097
952
  }
1098
953
 
1099
954
  sendDTMF(session: WazoSession, tone: string): boolean {
@@ -1102,14 +957,14 @@ export default class WebRTCClient extends Emitter {
1102
957
  }
1103
958
 
1104
959
  logger.info('Sending DTMF', {
1105
- id: this.getSipSessionId(session),
960
+ id: getSipSessionId(session),
1106
961
  tone,
1107
962
  });
1108
963
  return session.sessionDescriptionHandler.sendDtmf(tone);
1109
964
  }
1110
965
 
1111
966
  message(destination: string, message: string): void {
1112
- const uri = this._makeURI(destination);
967
+ const uri = makeURI(destination, this.config.host);
1113
968
  if (!this.userAgent || !uri) {
1114
969
  logger.warn('Null value on message', { uri, userAgent: this.userAgent });
1115
970
  return;
@@ -1121,7 +976,7 @@ export default class WebRTCClient extends Emitter {
1121
976
  transfer(session: WazoSession, target: string): void {
1122
977
  this.hold(session);
1123
978
  logger.info('Transfering a session', {
1124
- id: this.getSipSessionId(session),
979
+ id: getSipSessionId(session),
1125
980
  target,
1126
981
  });
1127
982
  const options = {
@@ -1132,7 +987,7 @@ export default class WebRTCClient extends Emitter {
1132
987
  },
1133
988
  };
1134
989
  setTimeout(() => {
1135
- const uri = this._makeURI(target);
990
+ const uri = makeURI(target, this.config.host);
1136
991
  if (!uri) {
1137
992
  logger.warn('transfer timeout: null URI');
1138
993
  return;
@@ -1145,21 +1000,21 @@ export default class WebRTCClient extends Emitter {
1145
1000
  atxfer(session: WazoSession): Record<string, any> {
1146
1001
  this.hold(session);
1147
1002
  logger.info('webrtc transfer started', {
1148
- id: this.getSipSessionId(session),
1003
+ id: getSipSessionId(session),
1149
1004
  });
1150
1005
  const result: Record<string, any> = {
1151
1006
  newSession: null,
1152
1007
  init: async (target: string) => {
1153
1008
  logger.info('webrtc transfer initialized', {
1154
- id: this.getSipSessionId(session),
1009
+ id: getSipSessionId(session),
1155
1010
  target,
1156
1011
  });
1157
1012
  result.newSession = await this.call(target);
1158
1013
  },
1159
1014
  complete: () => {
1160
1015
  logger.info('webrtc transfer completed', {
1161
- id: this.getSipSessionId(session),
1162
- referId: this.getSipSessionId(result.newSession),
1016
+ id: getSipSessionId(session),
1017
+ referId: getSipSessionId(result.newSession),
1163
1018
  });
1164
1019
  session.refer(result.newSession);
1165
1020
  },
@@ -1205,7 +1060,7 @@ export default class WebRTCClient extends Emitter {
1205
1060
 
1206
1061
  const core = this.userAgent?.userAgentCore;
1207
1062
 
1208
- const fromURI = this._makeURI(this.config.authorizationUser || '');
1063
+ const fromURI = makeURI(this.config.authorizationUser || '', this.config.host);
1209
1064
 
1210
1065
  const toURI = new URI('sip', '', this.config.host);
1211
1066
  if (fromURI) {
@@ -1229,10 +1084,6 @@ export default class WebRTCClient extends Emitter {
1229
1084
  return this.userAgent ? `${this.userAgent.configuration.contactName}/${this.userAgent.contact.uri}` : null;
1230
1085
  }
1231
1086
 
1232
- isFirefox(): boolean {
1233
- return this._isWeb() && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
1234
- }
1235
-
1236
1087
  changeAudioOutputVolume(volume: number): void {
1237
1088
  logger.info('Changing audio output volume', {
1238
1089
  volume,
@@ -1356,13 +1207,13 @@ export default class WebRTCClient extends Emitter {
1356
1207
  }
1357
1208
 
1358
1209
  changeSessionVideoInputDevice(id: string | null | undefined, session: WazoSession): Promise<MediaStream | void> {
1359
- if (!this.sessionWantsToDoVideo(session)) {
1210
+ if (!sessionWantsToDoVideo(session)) {
1360
1211
  return Promise.resolve();
1361
1212
  }
1362
1213
 
1363
1214
  const sdh = session.sessionDescriptionHandler as SessionDescriptionHandler;
1364
1215
  const pc = sdh.peerConnection;
1365
- const sessionId = this.getSipSessionId(session);
1216
+ const sessionId = getSipSessionId(session);
1366
1217
  const localStream = this.getLocalStream(sessionId);
1367
1218
  logger.info('changing video input device', {
1368
1219
  id,
@@ -1406,7 +1257,7 @@ export default class WebRTCClient extends Emitter {
1406
1257
 
1407
1258
  // let's update the local stream
1408
1259
  this.eventEmitter.emit('onVideoInputChange', stream);
1409
- this.setLocalMediaStream(sessionId, stream);
1260
+ setLocalMediaStream(this.sipSessions[sessionId], stream);
1410
1261
  return stream;
1411
1262
  });
1412
1263
  }
@@ -1430,12 +1281,12 @@ export default class WebRTCClient extends Emitter {
1430
1281
  }
1431
1282
 
1432
1283
  const wasMuted = this.isAudioMuted(sipSession);
1433
- const shouldDoVideo = newConstraints ? newConstraints.video : this.sessionWantsToDoVideo(sipSession);
1284
+ const shouldDoVideo = newConstraints ? newConstraints.video : sessionWantsToDoVideo(sipSession);
1434
1285
  const shouldDoScreenSharing = newConstraints && newConstraints.screen;
1435
1286
  const desktop = newConstraints && newConstraints.desktop;
1436
1287
  logger.info('Sending reinvite', {
1437
1288
  clientId: this.clientId,
1438
- id: this.getSipSessionId(sipSession),
1289
+ id: getSipSessionId(sipSession),
1439
1290
  newConstraints,
1440
1291
  conference,
1441
1292
  audioOnly,
@@ -1457,7 +1308,7 @@ export default class WebRTCClient extends Emitter {
1457
1308
  } as SessionDescriptionHandlerOptions;
1458
1309
  const {
1459
1310
  constraints,
1460
- } = this.getMediaConfiguration(shouldDoVideo, conference, newConstraints);
1311
+ } = getMediaConfiguration(shouldDoVideo, conference, newConstraints);
1461
1312
  return sipSession.invite({
1462
1313
  requestDelegate: {
1463
1314
  onAccept: (response: IncomingResponse) => {
@@ -1473,11 +1324,11 @@ export default class WebRTCClient extends Emitter {
1473
1324
  }
1474
1325
 
1475
1326
  logger.info('on re-INVITE accepted', {
1476
- id: this.getSipSessionId(sipSession),
1327
+ id: getSipSessionId(sipSession),
1477
1328
  wasMuted,
1478
1329
  shouldDoScreenSharing,
1479
1330
  });
1480
- this.updateRemoteStream(this.getSipSessionId(sipSession), false);
1331
+ updateRemoteStream(sipSession, false);
1481
1332
 
1482
1333
  if (wasMuted) {
1483
1334
  this.mute(sipSession);
@@ -1516,135 +1367,70 @@ export default class WebRTCClient extends Emitter {
1516
1367
  }
1517
1368
 
1518
1369
  getPeerConnection(sessionId: string) {
1519
- const sipSession = this.sipSessions[sessionId];
1520
-
1521
- if (!sipSession) {
1522
- return null;
1523
- }
1524
-
1525
- return sipSession.sessionDescriptionHandler ? (sipSession.sessionDescriptionHandler as SessionDescriptionHandler).peerConnection : null;
1370
+ return getPeerConnection(this.sipSessions[sessionId]);
1526
1371
  }
1527
1372
 
1528
1373
  // Local streams
1529
1374
  getLocalStream(sessionId: string): MediaStream | null {
1530
- const sipSession = this.sipSessions[sessionId];
1531
- return (sipSession?.sessionDescriptionHandler as SessionDescriptionHandler)?.localMediaStream || null;
1375
+ return getLocalStream(this.sipSessions[sessionId]);
1532
1376
  }
1533
1377
 
1534
1378
  getLocalTracks(sessionId: string): MediaStreamTrack[] {
1535
- const localStream = this.getLocalStream(sessionId);
1536
-
1537
- if (!localStream) {
1538
- return [];
1539
- }
1540
-
1541
- return localStream.getTracks();
1379
+ return getLocalTracks(this.sipSessions[sessionId]);
1542
1380
  }
1543
1381
 
1544
1382
  hasLocalVideo(sessionId: string): boolean {
1545
- return this.getLocalTracks(sessionId).some(this._isVideoTrack);
1383
+ return hasLocalVideo(this.sipSessions[sessionId]);
1546
1384
  }
1547
1385
 
1548
1386
  // Check if we have at least one video track, even muted or not live
1549
1387
  hasALocalVideoTrack(sessionId: string): boolean {
1550
- return this.getLocalTracks(sessionId).some(track => track.kind === 'video');
1388
+ return hasALocalVideoTrack(this.sipSessions[sessionId]);
1551
1389
  }
1552
1390
 
1553
1391
  getLocalVideoStream(sessionId: string): MediaStream | null | undefined {
1554
- return this.hasLocalVideo(sessionId) ? this.getLocalStream(sessionId) : null;
1392
+ return getLocalVideoStream(this.sipSessions[sessionId]);
1555
1393
  }
1556
1394
 
1557
1395
  // Remote streams
1558
1396
  getRemoteStream(sessionId: string): MediaStream | null {
1559
- const sipSession = this.sipSessions[sessionId];
1560
- return (sipSession?.sessionDescriptionHandler as SessionDescriptionHandler)?.remoteMediaStream || null;
1397
+ return getRemoteStream(this.sipSessions[sessionId]);
1561
1398
  }
1562
1399
 
1563
1400
  getRemoteTracks(sessionId: string): MediaStreamTrack[] {
1564
- const remoteStream = this.getRemoteStream(sessionId);
1565
-
1566
- if (!remoteStream) {
1567
- return [];
1568
- }
1569
-
1570
- return remoteStream.getTracks();
1401
+ return getRemoteTracks(this.sipSessions[sessionId]);
1571
1402
  }
1572
1403
 
1573
1404
  hasRemoteVideo(sessionId: string): boolean {
1574
- return this.getRemoteTracks(sessionId).some(this._isVideoTrack);
1405
+ return hasRemoteVideo(this.sipSessions[sessionId]);
1575
1406
  }
1576
1407
 
1577
1408
  // Check if we have at least one video track, even muted or not live
1578
1409
  hasARemoteVideoTrack(sessionId: string): boolean {
1579
- return this.getRemoteTracks(sessionId).some(track => track.kind === 'video');
1410
+ return hasARemoteVideoTrack(this.sipSessions[sessionId]);
1580
1411
  }
1581
1412
 
1582
1413
  getRemoteVideoStream(sessionId: string): MediaStream | null | undefined {
1583
- if (this.isVideoRemotelyHeld(sessionId)) {
1584
- return null;
1585
- }
1586
-
1587
- return this.hasRemoteVideo(sessionId) ? this.getRemoteStream(sessionId) : null;
1414
+ return getRemoteVideoStream(this.sipSessions[sessionId]);
1588
1415
  }
1589
1416
 
1590
1417
  // Useful in a react-native environment when remoteMediaStream is not updated
1591
1418
  getRemoteVideoStreamFromPc(sessionId: string): MediaStream | null | undefined {
1592
- const pc = this.getPeerConnection(sessionId) as PeerConnection;
1593
-
1594
- if (!pc) {
1595
- return null;
1596
- }
1597
-
1598
- return pc.getRemoteStreams().find((stream: MediaStream) => !!stream.getVideoTracks().length);
1419
+ return getRemoteVideoStreamFromPc(this.sipSessions[sessionId]);
1599
1420
  }
1600
1421
 
1601
1422
  hasVideo(sessionId: string): boolean {
1602
- return this.hasLocalVideo(sessionId) || this.hasRemoteVideo(sessionId);
1423
+ return hasVideoTrack(this.sipSessions[sessionId]);
1603
1424
  }
1604
1425
 
1605
1426
  hasAVideoTrack(sessionId: string): boolean {
1606
- return this.hasALocalVideoTrack(sessionId) || this.hasARemoteVideoTrack(sessionId);
1607
- }
1608
-
1609
- getSipSessionId(sipSession: WazoSession | null | undefined): string {
1610
- if (!sipSession) {
1611
- return '';
1612
- }
1613
-
1614
- const message = sipSession.message as Partial<{ callId?: string }>;
1615
- if (message?.callId) {
1616
- return message.callId.substring(0, SIP_ID_LENGTH);
1617
- }
1618
-
1619
- // For Inviter
1620
- // @ts-ignore: private
1621
- if (sipSession instanceof Inviter && sipSession.outgoingRequestMessage) {
1622
- // @ts-ignore: private
1623
- return sipSession.outgoingRequestMessage.callId.substring(0, SIP_ID_LENGTH);
1624
- }
1625
-
1626
- // For Invitation
1627
- return (sipSession.id || '').substring(0, SIP_ID_LENGTH);
1427
+ return hasAVideoTrack(this.sipSessions[sessionId]);
1628
1428
  }
1629
1429
 
1630
1430
  async waitForRegister(): Promise<void> {
1631
1431
  return new Promise(resolve => this.on(REGISTERED, resolve));
1632
1432
  }
1633
1433
 
1634
- // eslint-disable-next-line no-unused-vars
1635
- sessionWantsToDoVideo(session: WazoSession): boolean {
1636
- if (!session) {
1637
- return false;
1638
- }
1639
-
1640
- const {
1641
- body,
1642
- } = session.request || session;
1643
- // Sometimes with InviteClientContext the body is in the body attribute ...
1644
- const sdp = typeof body === 'object' && body ? body.body : body;
1645
- return hasAnActiveVideo(sdp);
1646
- }
1647
-
1648
1434
  hasHeartbeat(): boolean {
1649
1435
  return this.heartbeat.hasHeartbeat;
1650
1436
  }
@@ -1678,7 +1464,7 @@ export default class WebRTCClient extends Emitter {
1678
1464
  onCallEnded(session: WazoSession): void {
1679
1465
  this._cleanupMedia(session);
1680
1466
 
1681
- delete this.sipSessions[this.getSipSessionId(session)];
1467
+ delete this.sipSessions[getSipSessionId(session)];
1682
1468
 
1683
1469
  this._stopSendingStats(session);
1684
1470
 
@@ -1697,7 +1483,7 @@ export default class WebRTCClient extends Emitter {
1697
1483
  }
1698
1484
 
1699
1485
  storeSipSession(session: WazoSession): void {
1700
- const id = this.getSipSessionId(session);
1486
+ const id = getSipSessionId(session);
1701
1487
  logger.info('storing sip session', { id, clientId: this.clientId });
1702
1488
 
1703
1489
  this.sipSessions[id] = session;
@@ -1711,24 +1497,6 @@ export default class WebRTCClient extends Emitter {
1711
1497
  return Object.keys(this.sipSessions);
1712
1498
  }
1713
1499
 
1714
- setLocalMediaStream(sipSessionId: string, newStream: MediaStream): void {
1715
- const sipSession = this.sipSessions[sipSessionId];
1716
-
1717
- if (!sipSession) {
1718
- return;
1719
- }
1720
-
1721
- logger.info('setting local media stream', {
1722
- sipSessionId,
1723
- tracks: newStream ? newStream.getTracks() : null,
1724
- });
1725
- if (sipSession.sessionDescriptionHandler) {
1726
- // eslint-disable-next-line no-underscore-dangle
1727
- // @ts-ignore: protected
1728
- sipSession.sessionDescriptionHandler._localMediaStream = newStream;
1729
- }
1730
- }
1731
-
1732
1500
  async updateLocalStream(sipSessionId: string, newStream: MediaStream): Promise<void> {
1733
1501
  const sipSession = this.sipSessions[sipSessionId];
1734
1502
 
@@ -1744,84 +1512,15 @@ export default class WebRTCClient extends Emitter {
1744
1512
  });
1745
1513
 
1746
1514
  if (oldStream) {
1747
- this._cleanupStream(oldStream);
1515
+ cleanupStream(oldStream);
1748
1516
  }
1749
1517
 
1750
- this.setLocalMediaStream(sipSessionId, newStream);
1518
+ setLocalMediaStream(sipSession, newStream);
1751
1519
 
1752
1520
  // Update the local video element
1753
1521
  await this._setupMedias(sipSession, newStream);
1754
1522
  }
1755
1523
 
1756
- updateRemoteStream(sessionId: string, allTracks = true): void {
1757
- const remoteStream = this.getRemoteStream(sessionId);
1758
- const pc = this.getPeerConnection(sessionId);
1759
- logger.info('Updating remote stream', {
1760
- sessionId,
1761
- tracks: remoteStream ? remoteStream.getTracks() : null,
1762
- receiverTracks: pc && pc.getReceivers ? pc.getReceivers().map((receiver) => receiver.track) : null,
1763
- });
1764
-
1765
- if (!pc || !remoteStream) {
1766
- return;
1767
- }
1768
-
1769
- remoteStream.getTracks().forEach(track => {
1770
- if (allTracks || track.kind === 'video') {
1771
- remoteStream.removeTrack(track);
1772
- }
1773
- });
1774
-
1775
- if (pc.getReceivers) {
1776
- pc.getReceivers().forEach((receiver) => {
1777
- if (allTracks || receiver.track.kind === 'video') {
1778
- remoteStream.addTrack(receiver.track);
1779
- }
1780
- });
1781
- }
1782
- }
1783
-
1784
- getMediaConfiguration(enableVideo: boolean, conference = false, constraints: Record<string, any> | null | undefined = null): Record<string, any> {
1785
- const screenSharing = constraints && 'screen' in constraints ? constraints.screen : false;
1786
- const isDesktop = constraints && 'desktop' in constraints ? constraints.desktop : false;
1787
- const withAudio = constraints && 'audio' in constraints ? constraints.audio : true;
1788
- const mandatoryVideo = constraints && typeof constraints.video === 'object' ? constraints.video.mandatory : {};
1789
- logger.info('Retrieving media a configuration', {
1790
- enableVideo,
1791
- screenSharing,
1792
- isDesktop,
1793
- withAudio,
1794
- constraints,
1795
- });
1796
- return {
1797
- constraints: {
1798
- // Exact constraint are not supported with `getDisplayMedia` and we must have a video=false in desktop screenshare
1799
- audio: screenSharing ? !isDesktop : withAudio ? this._getAudioConstraints() : false,
1800
- video: screenSharing ? isDesktop ? {
1801
- mandatory: {
1802
- chromeMediaSource: 'desktop',
1803
- ...(mandatoryVideo || {}),
1804
- },
1805
- } : {
1806
- cursor: 'always',
1807
- } : this._getVideoConstraints(enableVideo),
1808
- screen: screenSharing,
1809
- desktop: isDesktop,
1810
- conference,
1811
- },
1812
- enableVideo,
1813
- conference,
1814
- offerOptions: {
1815
- OfferToReceiveAudio: this._hasAudio(),
1816
- OfferToReceiveVideo: enableVideo,
1817
- mandatory: {
1818
- OfferToReceiveAudio: this._hasAudio(),
1819
- OfferToReceiveVideo: enableVideo,
1820
- },
1821
- },
1822
- };
1823
- }
1824
-
1825
1524
  isConference(sessionId: string): boolean {
1826
1525
  return sessionId in this.conferences;
1827
1526
  }
@@ -1913,30 +1612,16 @@ export default class WebRTCClient extends Emitter {
1913
1612
  }
1914
1613
  }
1915
1614
 
1916
- _isWeb(): boolean {
1917
- return typeof window === 'object' && typeof document === 'object';
1918
- }
1919
-
1920
- _isVideoTrack(track: MediaStreamTrack): boolean {
1921
- return track.kind === 'video' && track.readyState === 'live';
1922
- }
1923
-
1924
1615
  _hasAudio(): boolean {
1925
1616
  return this.hasAudio;
1926
1617
  }
1927
1618
 
1928
1619
  _getAudioConstraints(): MediaTrackConstraints | boolean {
1929
- // @ts-ignore: media constraints
1930
- return this.audio?.deviceId?.exact ? this.audio : true;
1620
+ return getAudioConstraints(this.audio);
1931
1621
  }
1932
1622
 
1933
1623
  _getVideoConstraints(video = false): MediaTrackConstraints | boolean {
1934
- if (!video) {
1935
- return false;
1936
- }
1937
-
1938
- // @ts-ignore: media constraints
1939
- return this.video?.deviceId?.exact ? this.video : true;
1624
+ return getVideoConstraints(video && this.video);
1940
1625
  }
1941
1626
 
1942
1627
  _connectIfNeeded(): Promise<any> {
@@ -1999,101 +1684,9 @@ export default class WebRTCClient extends Emitter {
1999
1684
  }));
2000
1685
  }
2001
1686
 
2002
- _createUaOptions(uaOptionsOverrides: UserAgentConfigOverrides = {}): UserAgentOptions {
2003
- let {
2004
- host,
2005
- } = this.config;
2006
- let port: number | string = this.config.port || 443;
2007
-
2008
- if (this.config.websocketSip) {
2009
- const webSocketSip = this.config.websocketSip.split(':');
2010
- [host] = webSocketSip;
2011
- port = Number(webSocketSip[1]);
2012
- }
2013
- const { userUuid } = this.config;
2014
-
2015
- const uaOptions: UserAgentOptions = {
2016
- noAnswerTimeout: NO_ANSWER_TIMEOUT,
2017
- authorizationUsername: this.config.authorizationUser,
2018
- authorizationPassword: this.config.password,
2019
- displayName: this.config.displayName,
2020
- hackIpInContact: true,
2021
- contactParams: {
2022
- transport: 'wss',
2023
- },
2024
- logBuiltinEnabled: this.config.log ? this.config.log.builtinEnabled : null,
2025
- logLevel: this.config.log ? this.config.log.logLevel : null,
2026
- logConnector: this.config.log ? this.config.log.connector : null,
2027
- uri: this._makeURI(this.config.authorizationUser || ''),
2028
- userAgentString: this.config.userAgentString || 'wazo-sdk',
2029
- reconnectionAttempts: 10000,
2030
- reconnectionDelay: 5,
2031
-
2032
- sessionDescriptionHandlerFactory: (session: Session, options: SessionDescriptionHandlerFactoryOptions = {}) => {
2033
- const uaLogger = session.userAgent.getLogger('sip.WazoSessionDescriptionHandler');
2034
-
2035
- const isWeb = this._isWeb();
2036
-
2037
- const iceGatheringTimeout = 'peerConnectionOptions' in options ? (options.peerConnectionOptions as any).iceGatheringTimeout || DEFAULT_ICE_TIMEOUT : DEFAULT_ICE_TIMEOUT;
2038
- const sdhOptions: SessionDescriptionHandlerConfiguration = { ...options,
2039
- iceGatheringTimeout,
2040
- peerConnectionConfiguration: { ...defaultPeerConnectionConfiguration(),
2041
- ...(options.peerConnectionConfiguration || {}),
2042
- },
2043
- };
2044
- return new WazoSessionDescriptionHandler(uaLogger, wazoMediaStreamFactory, sdhOptions, isWeb, session as WazoSession);
2045
- },
2046
- transportOptions: {
2047
- traceSip: uaOptionsOverrides?.traceSip || false,
2048
- wsServers: `wss://${host}:${port}/api/asterisk/ws${userUuid ? `?userUuid=${userUuid}` : ''}`,
2049
- },
2050
- sessionDescriptionHandlerFactoryOptions: {
2051
- modifiers: [replaceLocalIpModifier],
2052
- alwaysAcquireMediaFirst: this.isFirefox(),
2053
- constraints: {
2054
- audio: this._getAudioConstraints(),
2055
- video: this._getVideoConstraints(),
2056
- },
2057
- peerConnectionOptions: {
2058
- iceCheckingTimeout: this.config.iceCheckingTimeout || 1000,
2059
- iceGatheringTimeout: this.config.iceCheckingTimeout || 1000,
2060
- rtcConfiguration: {
2061
- rtcpMuxPolicy: 'require',
2062
- iceServers: WebRTCClient.getIceServers(this.config.host),
2063
- ...this._getRtcOptions(),
2064
- ...(uaOptionsOverrides?.peerConnectionOptions || {}),
2065
- },
2066
- },
2067
- // Configuration used in SDH to create the PeerConnection
2068
- peerConnectionConfiguration: {
2069
- rtcpMuxPolicy: 'require',
2070
- iceServers: WebRTCClient.getIceServers(this.config.host),
2071
- ...(uaOptionsOverrides?.peerConnectionOptions || {}),
2072
- },
2073
- },
2074
- };
2075
-
2076
- delete uaOptionsOverrides?.traceSip;
2077
-
2078
- return {
2079
- ...uaOptions,
2080
- ...uaOptionsOverrides,
2081
- };
2082
- }
2083
-
2084
- // eslint-disable-next-line no-unused-vars
2085
- _getRtcOptions() {
2086
- return {
2087
- mandatory: {
2088
- OfferToReceiveAudio: this._hasAudio(),
2089
- OfferToReceiveVideo: false,
2090
- },
2091
- };
2092
- }
2093
-
2094
1687
  // Invitation and Inviter extends Session
2095
1688
  _setupSession(session: WazoSession): void {
2096
- const sipSessionId = this.getSipSessionId(session);
1689
+ const sipSessionId = getSipSessionId(session);
2097
1690
 
2098
1691
  // When receiving an Invitation, the delegate is not defined.
2099
1692
  if (!session.delegate) {
@@ -2102,7 +1695,7 @@ export default class WebRTCClient extends Emitter {
2102
1695
 
2103
1696
  session.delegate.onSessionDescriptionHandler = (sdh: SessionDescriptionHandler & { on: (type: string, e: any) => void }) => {
2104
1697
  sdh.on('error', (e: any) => {
2105
- this.eventEmitter.emit(ON_ERROR, e);
1698
+ this.eventEmitter.emit(ON_ERROR, e, session);
2106
1699
  });
2107
1700
  sdh.peerConnectionDelegate = {
2108
1701
  onicecandidateerror: (error: Record<string, any>) => {
@@ -2157,7 +1750,7 @@ export default class WebRTCClient extends Emitter {
2157
1750
  session.outgoingInviteRequest.message.body.body = request.body;
2158
1751
  }
2159
1752
 
2160
- this.updateRemoteStream(sipSessionId, false);
1753
+ updateRemoteStream(this.sipSessions[sipSessionId], false);
2161
1754
 
2162
1755
  await this._setupMedias(session);
2163
1756
 
@@ -2168,8 +1761,8 @@ export default class WebRTCClient extends Emitter {
2168
1761
  async _onProgress(session: WazoSession, early = false): Promise<void> {
2169
1762
  await this._setupMedias(session);
2170
1763
 
2171
- const sessionId = this.getSipSessionId(session).substr(0, 20);
2172
- this.updateRemoteStream(sessionId);
1764
+ const sessionId = getSipSessionId(session).substr(0, 20);
1765
+ updateRemoteStream(session);
2173
1766
  logger.info('progress received', {
2174
1767
  sessionId,
2175
1768
  early,
@@ -2187,30 +1780,16 @@ export default class WebRTCClient extends Emitter {
2187
1780
 
2188
1781
  await this._setupMedias(session);
2189
1782
 
2190
- this.updateRemoteStream(this.getSipSessionId(session), initAllTracks);
2191
- const pc = (session.sessionDescriptionHandler as SessionDescriptionHandler)?.peerConnection;
1783
+ updateRemoteStream(session, initAllTracks);
1784
+ const pc = getPeerConnection(session);
2192
1785
 
2193
1786
  const onTrack = (event: RTCTrackEvent | MediaStreamTrackEvent) => {
2194
- const isAudioOnly = this._isAudioOnly(session);
2195
-
2196
- const {
2197
- kind,
2198
- label,
2199
- readyState,
2200
- id,
2201
- muted,
2202
- } = event.track;
2203
- logger.info('on track event', {
2204
- isAudioOnly,
2205
- kind,
2206
- label,
2207
- readyState,
2208
- id,
2209
- muted,
2210
- });
1787
+ const audioOnly = isAudioOnly(session);
1788
+ const { kind, label, readyState, id, muted } = event.track;
1789
+ logger.info('on track event', { audioOnly, kind, label, readyState, id, muted });
2211
1790
 
2212
1791
  // Stop video track in audio only mode
2213
- if (isAudioOnly && kind === 'video') {
1792
+ if (audioOnly && kind === 'video') {
2214
1793
  event.track.stop();
2215
1794
  }
2216
1795
 
@@ -2243,18 +1822,14 @@ export default class WebRTCClient extends Emitter {
2243
1822
  this._startSendingStats(session);
2244
1823
  }
2245
1824
 
2246
- _isAudioOnly(session: WazoSession): boolean {
2247
- return Boolean(session.sessionDescriptionHandlerModifiersReInvite.find(modifier => modifier === stripVideo));
2248
- }
2249
-
2250
1825
  async _setupMedias(session: WazoSession, newStream: MediaStream | null | undefined = null): Promise<void> {
2251
- if (!this._isWeb()) {
1826
+ if (!isWeb()) {
2252
1827
  logger.info('Setup media on mobile, no need to setup html element, bailing');
2253
1828
  return;
2254
1829
  }
2255
1830
 
2256
1831
  // Safari hack, because you cannot call .play() from a non user action
2257
- const sessionId = this.getSipSessionId(session);
1832
+ const sessionId = getSipSessionId(session);
2258
1833
  const isConference = this.isConference(sessionId);
2259
1834
 
2260
1835
  if (sessionId in this.audioElements) {
@@ -2264,7 +1839,7 @@ export default class WebRTCClient extends Emitter {
2264
1839
  return;
2265
1840
  }
2266
1841
 
2267
- if (this._hasAudio() && this._isWeb() && !(sessionId in this.audioElements)) {
1842
+ if (this._hasAudio() && isWeb() && !(sessionId in this.audioElements)) {
2268
1843
  await this.createAudioElementFor(sessionId);
2269
1844
  }
2270
1845
 
@@ -2309,11 +1884,11 @@ export default class WebRTCClient extends Emitter {
2309
1884
  }
2310
1885
 
2311
1886
  _cleanupMedia(session?: WazoSession): void {
2312
- const sessionId = this.getSipSessionId(session);
1887
+ const sessionId = getSipSessionId(session);
2313
1888
  const localStream = this.getLocalStream(sessionId);
2314
1889
 
2315
1890
  if (localStream) {
2316
- this._cleanupStream(localStream);
1891
+ cleanupStream(localStream);
2317
1892
  }
2318
1893
 
2319
1894
  const cleanLocalElement = (id: string) => {
@@ -2333,90 +1908,15 @@ export default class WebRTCClient extends Emitter {
2333
1908
  delete this.audioElements[id];
2334
1909
  };
2335
1910
 
2336
- if (this._hasAudio() && this._isWeb()) {
1911
+ if (this._hasAudio() && isWeb()) {
2337
1912
  if (session) {
2338
- cleanLocalElement(this.getSipSessionId(session));
1913
+ cleanLocalElement(getSipSessionId(session));
2339
1914
  } else {
2340
1915
  Object.keys(this.audioElements).forEach(id => cleanLocalElement(id));
2341
1916
  }
2342
1917
  }
2343
1918
  }
2344
1919
 
2345
- _cleanupStream(stream: MediaStream): void {
2346
- stream.getTracks().filter(track => track.enabled).forEach(track => track.stop());
2347
- }
2348
-
2349
- _toggleAudio(session: WazoSession, muteAudio: boolean): void {
2350
- const sdh = session.sessionDescriptionHandler as SessionDescriptionHandler;
2351
- const pc = (sdh?.peerConnection || null) as PeerConnection;
2352
-
2353
- if (!pc) {
2354
- return;
2355
- }
2356
-
2357
- if (pc.getSenders) {
2358
- pc.getSenders().forEach((sender) => {
2359
- if (sender && sender.track && sender.track.kind === 'audio') {
2360
- // eslint-disable-next-line
2361
- sender.track.enabled = !muteAudio;
2362
- }
2363
- });
2364
- } else {
2365
- pc.getLocalStreams().forEach((stream: MediaStream) => {
2366
- stream.getAudioTracks().forEach(track => {
2367
- // eslint-disable-next-line
2368
- track.enabled = !muteAudio;
2369
- });
2370
- });
2371
- }
2372
- }
2373
-
2374
- _toggleVideo(session: WazoSession, muteCamera: boolean): void {
2375
- const sdh = session.sessionDescriptionHandler as SessionDescriptionHandler;
2376
- const pc = sdh?.peerConnection as PeerConnection;
2377
-
2378
- if (pc?.getSenders) {
2379
- pc.getSenders().forEach((sender) => {
2380
- if (sender && sender.track && sender.track.kind === 'video') {
2381
- // eslint-disable-next-line
2382
- sender.track.enabled = !muteCamera;
2383
- }
2384
- });
2385
- } else {
2386
- pc.getLocalStreams().forEach((stream: MediaStream) => {
2387
- stream.getVideoTracks().forEach(track => {
2388
- // eslint-disable-next-line
2389
- track.enabled = !muteCamera;
2390
- });
2391
- });
2392
- }
2393
- }
2394
-
2395
- /**
2396
- * @param pc RTCPeerConnection
2397
- */
2398
- _getRemoteStream(pc: PeerConnection): MediaStream | null {
2399
- let remoteStream: MediaStream | null = null;
2400
-
2401
- if (pc && pc.getReceivers) {
2402
- remoteStream = typeof global !== 'undefined' && global.window && global.window.MediaStream ? new global.window.MediaStream() : new window.MediaStream();
2403
-
2404
- pc.getReceivers().forEach((receiver) => {
2405
- const {
2406
- track,
2407
- } = receiver;
2408
-
2409
- if (track && remoteStream) {
2410
- remoteStream.addTrack(track);
2411
- }
2412
- });
2413
- } else if (pc) {
2414
- [remoteStream] = pc.getRemoteStreams();
2415
- }
2416
-
2417
- return remoteStream;
2418
- }
2419
-
2420
1920
  _cleanupRegister(): void {
2421
1921
  if (this.registerer) {
2422
1922
  // @ts-ignore: removeAllListeners does not exist
@@ -2433,7 +1933,7 @@ export default class WebRTCClient extends Emitter {
2433
1933
  return;
2434
1934
  }
2435
1935
 
2436
- const sessionId = this.getSipSessionId(session);
1936
+ const sessionId = getSipSessionId(session);
2437
1937
  getStats(pc, (result: Record<string, any>) => {
2438
1938
  const {
2439
1939
  results,
@@ -2450,7 +1950,7 @@ export default class WebRTCClient extends Emitter {
2450
1950
  }
2451
1951
 
2452
1952
  _stopSendingStats(session: WazoSession): void {
2453
- const sessionId = this.getSipSessionId(session);
1953
+ const sessionId = getSipSessionId(session);
2454
1954
  logger.trace('Check for stopping stats', {
2455
1955
  sessionId,
2456
1956
  ids: Object.keys(this.statsIntervals),
@@ -2465,10 +1965,6 @@ export default class WebRTCClient extends Emitter {
2465
1965
  }
2466
1966
  }
2467
1967
 
2468
- _makeURI(target: string): URI | undefined {
2469
- return UserAgent.makeURI(`sip:${target}@${this.config.host}`);
2470
- }
2471
-
2472
1968
  async _disconnectTransport(force = false) {
2473
1969
  const transport = this.userAgent?.transport as WazoTransport;
2474
1970
 
@@ -2494,110 +1990,14 @@ export default class WebRTCClient extends Emitter {
2494
1990
  }
2495
1991
 
2496
1992
  async _fetchNetworkStats(sessionId: string): Promise<Record<string, any> | null | undefined> {
2497
- const session = this.getSipSession(sessionId);
2498
- const stats = session ? await this.getStats(session) : null;
2499
-
2500
- if (!stats || !(sessionId in this.sessionNetworkStats)) {
1993
+ const session = this.sipSessions[sessionId];
1994
+ if (!(sessionId in this.sessionNetworkStats)) {
2501
1995
  return Promise.resolve(null);
2502
1996
  }
2503
1997
 
2504
- const networkStats: Record<string, any> = {
2505
- audioBytesSent: 0,
2506
- videoBytesSent: 0,
2507
- videoBytesReceived: 0,
2508
- audioBytesReceived: 0,
2509
- };
2510
1998
  const nbStats = this.sessionNetworkStats[sessionId].length;
2511
1999
  const lastNetworkStats = this.sessionNetworkStats[sessionId][nbStats - 1];
2512
- const lastAudioSent = lastNetworkStats ? lastNetworkStats.audioBytesSent : 0;
2513
- const lastAudioContentSent = lastNetworkStats ? lastNetworkStats.audioContentSent : 0;
2514
- const lastVideoSent = lastNetworkStats ? lastNetworkStats.videoBytesSent : 0;
2515
- const lastAudioReceived = lastNetworkStats ? lastNetworkStats.audioBytesReceived : 0;
2516
- const lastAudioContentReceived = lastNetworkStats ? lastNetworkStats.audioContentReceived : 0;
2517
- const lastVideoReceived = lastNetworkStats ? lastNetworkStats.videoBytesReceived : 0;
2518
- const lastTransportSent = lastNetworkStats ? lastNetworkStats.transportSent : 0;
2519
- const lastTransportReceived = lastNetworkStats ? lastNetworkStats.transportReceived : 0;
2520
- let audioBytesSent = 0; // content + header
2521
-
2522
- let audioBytesReceived = 0; // content + header
2523
-
2524
- let audioContentSent = 0; // useful to detect blank call
2525
-
2526
- let audioContentReceived = 0;
2527
- let videoBytesSent = 0;
2528
- let videoBytesReceived = 0;
2529
- let transportSent = 0;
2530
- let transportReceived = 0;
2531
- let packetsLost = 0;
2532
- stats.forEach(report => {
2533
- if (report.type === 'outbound-rtp' && report.kind === 'audio') {
2534
- audioBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
2535
- audioContentSent += Number(report.bytesSent);
2536
- }
2537
-
2538
- if (report.type === 'remote-outbound-rtp' && report.kind === 'audio') {
2539
- audioBytesSent += Number(report.bytesSent);
2540
- }
2541
-
2542
- if (report.type === 'outbound-rtp' && report.kind === 'video') {
2543
- videoBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
2544
- }
2545
-
2546
- if (report.type === 'inbound-rtp' && report.kind === 'audio') {
2547
- packetsLost += Number(report.packetsLost);
2548
- networkStats.packetsReceived = Number(report.packetsReceived);
2549
- audioBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
2550
- audioContentReceived += Number(report.bytesReceived);
2551
- }
2552
-
2553
- if (report.type === 'inbound-rtp' && report.kind === 'video') {
2554
- videoBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
2555
- }
2556
-
2557
- if (report.type === 'outbound-rtp' && report.kind === 'video') {
2558
- if ('framesPerSecond' in report) {
2559
- networkStats.framesPerSecond = Number(report.framesPerSecond);
2560
- }
2561
-
2562
- if ('framerateMean' in report) {
2563
- // framerateMean is only available in FF
2564
- networkStats.framesPerSecond = Math.round(report.framerateMean);
2565
- }
2566
- }
2567
-
2568
- if (report.type === 'remote-inbound-rtp' && report.kind === 'audio') {
2569
- packetsLost += Number(report.packetsLost);
2570
- networkStats.roundTripTime = Number(report.roundTripTime);
2571
- networkStats.jitter = Number(report.jitter);
2572
- }
2573
-
2574
- if (report.type === 'remote-inbound-rtp' && report.kind === 'video') {
2575
- packetsLost += Number(report.packetsLost);
2576
- }
2577
-
2578
- if (report.type === 'transport') {
2579
- transportSent += Number(report.bytesSent);
2580
- transportReceived += Number(report.bytesReceived);
2581
- }
2582
- });
2583
- networkStats.packetsLost = packetsLost;
2584
- networkStats.totalAudioBytesSent = audioBytesSent;
2585
- networkStats.audioBytesSent = audioBytesSent - lastAudioSent;
2586
- networkStats.totalAudioBytesReceived = audioBytesReceived - lastAudioReceived;
2587
- networkStats.audioBytesReceived = audioBytesReceived;
2588
- networkStats.totalAudioContentSent = audioContentSent;
2589
- networkStats.audioContentSent = audioContentSent - lastAudioContentSent;
2590
- networkStats.totalAudioBytesReceived = audioContentReceived - lastAudioContentReceived;
2591
- networkStats.audioContentReceived = audioContentReceived;
2592
- networkStats.totalVideoBytesSent = videoBytesSent;
2593
- networkStats.videoBytesSent = videoBytesSent - lastVideoSent;
2594
- networkStats.totalVideoBytesReceived = videoBytesReceived;
2595
- networkStats.videoBytesReceived = videoBytesReceived - lastVideoReceived;
2596
- networkStats.totalTransportSent = transportSent - lastTransportSent;
2597
- networkStats.transportSent = transportSent;
2598
- networkStats.totalTransportReceived = transportReceived;
2599
- networkStats.transportReceived = transportReceived - lastTransportReceived;
2600
- networkStats.bandwidth = networkStats.audioBytesSent + networkStats.audioBytesReceived + networkStats.videoBytesSent + networkStats.videoBytesReceived + networkStats.transportReceived + networkStats.transportSent;
2000
+ const networkStats = await fetchNetworkStats(session, lastNetworkStats);
2601
2001
 
2602
2002
  if (this.sessionNetworkStats[sessionId]) {
2603
2003
  this.eventEmitter.emit(ON_NETWORK_STATS, session, networkStats, this.sessionNetworkStats[sessionId]);