@wazo/sdk 0.43.5 → 1.0.0-alpha.0

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 (155) hide show
  1. package/dist/wazo-sdk.js +3 -3
  2. package/esm/__tests__/__voice__/call.test.js +72 -0
  3. package/esm/__tests__/__voice__/softphone.test.js +66 -0
  4. package/esm/api/auth.js +1 -5
  5. package/esm/checker/checks/webrtc.js +2 -1
  6. package/esm/domain/InvalidState.js +8 -0
  7. package/esm/domain/InvalidStateTransition.js +10 -0
  8. package/esm/domain/Phone/WebRTCPhone.js +33 -35
  9. package/esm/simple/Auth.js +12 -6
  10. package/esm/simple/Phone.js +2 -1
  11. package/esm/simple/__tests__/Auth.test.js +64 -8
  12. package/esm/simple/index.js +8 -2
  13. package/esm/simple/room/Room.js +2 -1
  14. package/esm/simple/room/SipRoom.js +2 -1
  15. package/esm/state-machine/call-state-machine.js +114 -0
  16. package/esm/state-machine/softphone-state-machine.js +39 -0
  17. package/esm/state-machine/utils.js +40 -0
  18. package/esm/types/__tests__/__voice__/call.test.d.ts +2 -0
  19. package/esm/types/__tests__/__voice__/call.test.d.ts.map +1 -0
  20. package/esm/types/__tests__/__voice__/softphone.test.d.ts +2 -0
  21. package/esm/types/__tests__/__voice__/softphone.test.d.ts.map +1 -0
  22. package/esm/types/api/auth.d.ts.map +1 -1
  23. package/esm/types/checker/checks/webrtc.d.ts.map +1 -1
  24. package/esm/types/domain/InvalidState.d.ts +6 -0
  25. package/esm/types/domain/InvalidState.d.ts.map +1 -0
  26. package/esm/types/domain/InvalidStateTransition.d.ts +7 -0
  27. package/esm/types/domain/InvalidStateTransition.d.ts.map +1 -0
  28. package/esm/types/domain/Phone/WebRTCPhone.d.ts +0 -1
  29. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  30. package/esm/types/domain/types.d.ts +4 -0
  31. package/esm/types/domain/types.d.ts.map +1 -1
  32. package/esm/types/simple/Auth.d.ts.map +1 -1
  33. package/esm/types/simple/Phone.d.ts.map +1 -1
  34. package/esm/types/simple/index.d.ts +7 -2
  35. package/esm/types/simple/index.d.ts.map +1 -1
  36. package/esm/types/simple/room/Room.d.ts.map +1 -1
  37. package/esm/types/simple/room/SipRoom.d.ts +1 -1
  38. package/esm/types/simple/room/SipRoom.d.ts.map +1 -1
  39. package/esm/types/state-machine/call-state-machine.d.ts +43 -0
  40. package/esm/types/state-machine/call-state-machine.d.ts.map +1 -0
  41. package/esm/types/state-machine/softphone-state-machine.d.ts +19 -0
  42. package/esm/types/state-machine/softphone-state-machine.d.ts.map +1 -0
  43. package/esm/types/state-machine/utils.d.ts +9 -0
  44. package/esm/types/state-machine/utils.d.ts.map +1 -0
  45. package/esm/types/utils/api-requester.d.ts.map +1 -1
  46. package/esm/types/utils/sdp.d.ts +6 -0
  47. package/esm/types/utils/sdp.d.ts.map +1 -1
  48. package/esm/types/utils/sip-logger.d.ts +4 -0
  49. package/esm/types/utils/sip-logger.d.ts.map +1 -0
  50. package/esm/types/utils/webrtc.d.ts +40 -0
  51. package/esm/types/utils/webrtc.d.ts.map +1 -1
  52. package/esm/types/voice/call.d.ts +110 -0
  53. package/esm/types/voice/call.d.ts.map +1 -0
  54. package/esm/types/voice/softphone.d.ts +77 -0
  55. package/esm/types/voice/softphone.d.ts.map +1 -0
  56. package/esm/types/web-rtc-client.d.ts +1 -36
  57. package/esm/types/web-rtc-client.d.ts.map +1 -1
  58. package/esm/utils/__tests__/api-requester.test.js +0 -17
  59. package/esm/utils/api-requester.js +1 -9
  60. package/esm/utils/sdp.js +26 -0
  61. package/esm/utils/sip-logger.js +32 -0
  62. package/esm/utils/webrtc.js +465 -0
  63. package/esm/voice/call.js +527 -0
  64. package/esm/voice/softphone.js +347 -0
  65. package/esm/web-rtc-client.js +86 -584
  66. package/lib/__tests__/__voice__/call.test.js +86 -0
  67. package/lib/__tests__/__voice__/softphone.test.js +80 -0
  68. package/lib/api/auth.js +1 -5
  69. package/lib/checker/checks/webrtc.js +2 -1
  70. package/lib/domain/InvalidState.js +9 -0
  71. package/lib/domain/InvalidStateTransition.js +10 -0
  72. package/lib/domain/Phone/WebRTCPhone.js +33 -35
  73. package/lib/simple/Auth.js +12 -6
  74. package/lib/simple/Phone.js +2 -1
  75. package/lib/simple/__tests__/Auth.test.js +64 -11
  76. package/lib/simple/index.js +6 -0
  77. package/lib/simple/room/Room.js +4 -3
  78. package/lib/simple/room/SipRoom.js +2 -1
  79. package/lib/state-machine/call-state-machine.js +120 -0
  80. package/lib/state-machine/softphone-state-machine.js +42 -0
  81. package/lib/state-machine/utils.js +52 -0
  82. package/lib/types/__tests__/__voice__/call.test.d.ts +2 -0
  83. package/lib/types/__tests__/__voice__/call.test.d.ts.map +1 -0
  84. package/lib/types/__tests__/__voice__/softphone.test.d.ts +2 -0
  85. package/lib/types/__tests__/__voice__/softphone.test.d.ts.map +1 -0
  86. package/lib/types/api/auth.d.ts.map +1 -1
  87. package/lib/types/checker/checks/webrtc.d.ts.map +1 -1
  88. package/lib/types/domain/InvalidState.d.ts +6 -0
  89. package/lib/types/domain/InvalidState.d.ts.map +1 -0
  90. package/lib/types/domain/InvalidStateTransition.d.ts +7 -0
  91. package/lib/types/domain/InvalidStateTransition.d.ts.map +1 -0
  92. package/lib/types/domain/Phone/WebRTCPhone.d.ts +0 -1
  93. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  94. package/lib/types/domain/types.d.ts +4 -0
  95. package/lib/types/domain/types.d.ts.map +1 -1
  96. package/lib/types/simple/Auth.d.ts.map +1 -1
  97. package/lib/types/simple/Phone.d.ts.map +1 -1
  98. package/lib/types/simple/index.d.ts +7 -2
  99. package/lib/types/simple/index.d.ts.map +1 -1
  100. package/lib/types/simple/room/Room.d.ts.map +1 -1
  101. package/lib/types/simple/room/SipRoom.d.ts +1 -1
  102. package/lib/types/simple/room/SipRoom.d.ts.map +1 -1
  103. package/lib/types/state-machine/call-state-machine.d.ts +43 -0
  104. package/lib/types/state-machine/call-state-machine.d.ts.map +1 -0
  105. package/lib/types/state-machine/softphone-state-machine.d.ts +19 -0
  106. package/lib/types/state-machine/softphone-state-machine.d.ts.map +1 -0
  107. package/lib/types/state-machine/utils.d.ts +9 -0
  108. package/lib/types/state-machine/utils.d.ts.map +1 -0
  109. package/lib/types/utils/api-requester.d.ts.map +1 -1
  110. package/lib/types/utils/sdp.d.ts +6 -0
  111. package/lib/types/utils/sdp.d.ts.map +1 -1
  112. package/lib/types/utils/sip-logger.d.ts +4 -0
  113. package/lib/types/utils/sip-logger.d.ts.map +1 -0
  114. package/lib/types/utils/webrtc.d.ts +40 -0
  115. package/lib/types/utils/webrtc.d.ts.map +1 -1
  116. package/lib/types/voice/call.d.ts +110 -0
  117. package/lib/types/voice/call.d.ts.map +1 -0
  118. package/lib/types/voice/softphone.d.ts +77 -0
  119. package/lib/types/voice/softphone.d.ts.map +1 -0
  120. package/lib/types/web-rtc-client.d.ts +1 -36
  121. package/lib/types/web-rtc-client.d.ts.map +1 -1
  122. package/lib/utils/__tests__/api-requester.test.js +0 -17
  123. package/lib/utils/api-requester.js +1 -9
  124. package/lib/utils/sdp.js +27 -1
  125. package/lib/utils/sip-logger.js +37 -0
  126. package/lib/utils/webrtc.js +521 -1
  127. package/lib/voice/call.js +577 -0
  128. package/lib/voice/softphone.js +375 -0
  129. package/lib/web-rtc-client.js +89 -601
  130. package/package.json +3 -2
  131. package/src/__tests__/__voice__/call.test.ts +90 -0
  132. package/src/__tests__/__voice__/softphone.test.ts +82 -0
  133. package/src/api/auth.ts +2 -13
  134. package/src/checker/checks/webrtc.ts +2 -1
  135. package/src/domain/InvalidState.ts +13 -0
  136. package/src/domain/InvalidStateTransition.ts +17 -0
  137. package/src/domain/Phone/WebRTCPhone.ts +34 -36
  138. package/src/domain/types.ts +6 -1
  139. package/src/simple/Auth.ts +13 -6
  140. package/src/simple/Phone.ts +2 -1
  141. package/src/simple/__tests__/Auth.test.ts +87 -9
  142. package/src/simple/index.ts +9 -2
  143. package/src/simple/room/Room.ts +2 -1
  144. package/src/simple/room/SipRoom.ts +2 -1
  145. package/src/state-machine/call-state-machine.ts +131 -0
  146. package/src/state-machine/softphone-state-machine.ts +49 -0
  147. package/src/state-machine/utils.ts +53 -0
  148. package/src/utils/__tests__/api-requester.test.ts +0 -20
  149. package/src/utils/api-requester.ts +1 -9
  150. package/src/utils/sdp.ts +36 -0
  151. package/src/utils/sip-logger.ts +40 -0
  152. package/src/utils/webrtc.ts +571 -0
  153. package/src/voice/call.ts +713 -0
  154. package/src/voice/softphone.ts +485 -0
  155. package/src/web-rtc-client.ts +90 -690
@@ -0,0 +1,72 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ import { Inviter, UserAgent } from 'sip.js';
3
+ import softphone from '../../voice/softphone';
4
+ import Call from '../../voice/call';
5
+ import { Actions, States } from '../../state-machine/call-state-machine';
6
+ import { Actions as SoftphoneActions } from '../../state-machine/softphone-state-machine';
7
+ import InvalidStateTransition from '../../domain/InvalidStateTransition';
8
+ const uaOptions = {
9
+ logBuiltinEnabled: false,
10
+ transportOptions: {
11
+ traceSip: false,
12
+ wsServers: 'ws://localhost',
13
+ },
14
+ };
15
+ jest.mock('../..', () => ({
16
+ __esModule: true,
17
+ default: {
18
+ Auth: {
19
+ getHost: jest.fn(() => 'my-stack.io'),
20
+ getSession: jest.fn(() => ({
21
+ displayName: () => 'John Doe',
22
+ primaryWebRtcLine: () => ({}),
23
+ })),
24
+ },
25
+ },
26
+ }));
27
+ jest.mock('../../web-rtc-client', () => jest.fn().mockImplementation(() => ({
28
+ answer: jest.fn(() => Promise.resolve()),
29
+ hangup: jest.fn(() => Promise.resolve()),
30
+ register: jest.fn(() => Promise.resolve()),
31
+ setOnHeartbeatTimeout: jest.fn(() => Promise.resolve()),
32
+ setOnHeartbeatCallback: jest.fn(() => Promise.resolve()),
33
+ on: jest.fn(),
34
+ })));
35
+ const ua = new UserAgent(uaOptions);
36
+ const sipCall = new Inviter(ua, UserAgent.makeURI('sip:1234@my.stack.io'));
37
+ softphone.connect();
38
+ softphone._sendAction(SoftphoneActions.REGISTER_DONE);
39
+ const invalidStateTransition = (state, action) => new InvalidStateTransition(`Invalid state transition from ${state} with action ${action}`, state, action);
40
+ describe('Call', () => {
41
+ describe('accept', () => {
42
+ it('Should throw an error when accepting a not ringing call', async () => {
43
+ const call = new Call(sipCall, softphone);
44
+ await expect(call.accept()).rejects.toThrowError(invalidStateTransition(States.IDLE, Actions.ACCEPT));
45
+ });
46
+ it.only('Should throw an error when the softphone is not registered', async () => {
47
+ const call = new Call(sipCall, softphone);
48
+ call._sendAction(Actions.INCOMING_CALL);
49
+ softphone._sendAction(SoftphoneActions.TRANSPORT_CLOSED);
50
+ await expect(call.accept()).rejects.toThrowError(invalidStateTransition(States.RINGING, Actions.ACCEPT));
51
+ });
52
+ it('Should not throw an error when accepting a not ringing call', async () => {
53
+ const call = new Call(sipCall, softphone);
54
+ call._sendAction(Actions.INCOMING_CALL);
55
+ await call.accept();
56
+ expect(softphone.client.answer).toHaveBeenCalled();
57
+ });
58
+ });
59
+ describe('hangup', () => {
60
+ it('Should throw an error when terminating a non established call', async () => {
61
+ const call = new Call(sipCall, softphone);
62
+ await expect(call.hangup()).rejects.toThrowError(invalidStateTransition(States.IDLE, Actions.HANGUP));
63
+ });
64
+ it('Should not throw an error when terminating an established call', async () => {
65
+ const call = new Call(sipCall, softphone);
66
+ call._sendAction(Actions.MAKE_CALL);
67
+ call._sendAction(Actions.REMOTLY_ACCEPTED);
68
+ await call.hangup();
69
+ expect(softphone.client.hangup).toHaveBeenCalled();
70
+ });
71
+ });
72
+ });
@@ -0,0 +1,66 @@
1
+ import { Softphone } from '../../voice/softphone';
2
+ import { Actions, States } from '../../state-machine/softphone-state-machine';
3
+ import Wazo from '../..';
4
+ import InvalidStateTransition from '../../domain/InvalidStateTransition';
5
+ const server = 'my.stack.io';
6
+ const displayName = 'John Doe';
7
+ const webRtcLine = {};
8
+ const session = {
9
+ uuid: '1234',
10
+ displayName: jest.fn(() => displayName),
11
+ primaryWebRtcLine: jest.fn(() => webRtcLine),
12
+ };
13
+ jest.mock('../..', () => ({
14
+ __esModule: true,
15
+ default: {
16
+ Auth: {
17
+ getHost: jest.fn(() => server),
18
+ getSession: jest.fn(() => session),
19
+ },
20
+ },
21
+ }));
22
+ jest.mock('../../web-rtc-client', () => jest.fn().mockImplementation(() => ({
23
+ register: jest.fn(() => Promise.resolve()),
24
+ unregister: jest.fn(() => Promise.resolve()),
25
+ setOnHeartbeatTimeout: jest.fn(() => Promise.resolve()),
26
+ setOnHeartbeatCallback: jest.fn(() => Promise.resolve()),
27
+ on: jest.fn(),
28
+ INVITE: 'invite',
29
+ })));
30
+ const invalidStateTransition = (state, action) => new InvalidStateTransition(`Invalid state transition from ${state} with action ${action}`, state, action);
31
+ describe('Softphone', () => {
32
+ describe('connect', () => {
33
+ it('Should retrieve information from Wazo.Auth', () => {
34
+ const instance = new Softphone();
35
+ jest.spyOn(instance, 'connectWithCredentials');
36
+ instance.connect({});
37
+ expect(Wazo.Auth.getHost).toHaveBeenCalled();
38
+ expect(Wazo.Auth.getSession).toHaveBeenCalled();
39
+ expect(instance.connectWithCredentials).toHaveBeenCalledWith(server, webRtcLine, displayName, { userUuid: session.uuid });
40
+ expect(instance.client.on).toHaveBeenCalledWith('invite', expect.anything());
41
+ });
42
+ it('Should throw an error when already connected', async () => {
43
+ const instance = new Softphone();
44
+ instance.softphoneActor.send({ type: Actions.REGISTER });
45
+ await expect(instance.connect({})).rejects.toThrowError(invalidStateTransition(States.REGISTERING, Actions.REGISTER));
46
+ });
47
+ it('Should not allow to register twice', async () => {
48
+ const instance = new Softphone();
49
+ instance.connect({});
50
+ await expect(instance.connect({})).rejects.toThrowError(invalidStateTransition(States.REGISTERING, Actions.REGISTER));
51
+ });
52
+ });
53
+ describe('disconnect', () => {
54
+ it('Should throw an error when not connected', async () => {
55
+ const instance = new Softphone();
56
+ await expect(instance.disconnect()).rejects.toThrowError(invalidStateTransition(States.UNREGISTERED, Actions.UNREGISTER));
57
+ });
58
+ it('Should unregister the client', async () => {
59
+ const instance = new Softphone();
60
+ instance.connect({});
61
+ instance.softphoneActor.send({ type: Actions.REGISTER_DONE });
62
+ await instance.disconnect();
63
+ expect(instance.client.unregister).toHaveBeenCalled();
64
+ });
65
+ });
66
+ });
package/esm/api/auth.js CHANGED
@@ -36,10 +36,6 @@ export default ((client, baseUrl) => ({
36
36
  const body = {
37
37
  saml_session_id: samlSessionId,
38
38
  };
39
- if (client.clientId) {
40
- body.access_type = 'offline';
41
- body.client_id = client.clientId;
42
- }
43
39
  return client.post(`${baseUrl}/token`, body).then(Session.parse);
44
40
  },
45
41
  initiateIdpAuthentication: async (domain, redirectUrl) => {
@@ -51,7 +47,7 @@ export default ((client, baseUrl) => ({
51
47
  Accept: 'application/json',
52
48
  'Content-Type': 'application/json',
53
49
  };
54
- return client.post(`${baseUrl}/saml/sso`, body, headers);
50
+ return client.post(`${baseUrl}/saml/sso`, body, headers, (response) => response);
55
51
  },
56
52
  refreshToken: (refreshToken, backend, expiration, isMobile, tenantId, domainName) => {
57
53
  const body = {
@@ -1,4 +1,5 @@
1
1
  /* global MediaStream */
2
+ import { getSipSessionId } from '../../utils/sdp';
2
3
  import WebRTCClient from '../../web-rtc-client';
3
4
  export default {
4
5
  name: 'WebRTC',
@@ -28,7 +29,7 @@ export default {
28
29
  });
29
30
  client.on(client.REGISTERED, () => {
30
31
  const sipSession = client.call('*10');
31
- if (!sipSession || !client.getSipSessionId(sipSession)) {
32
+ if (!sipSession || !getSipSessionId(sipSession)) {
32
33
  return handleError('Unable to make call through WebRTC');
33
34
  }
34
35
  setTimeout(() => {
@@ -0,0 +1,8 @@
1
+ class InvalidState extends Error {
2
+ state;
3
+ constructor(message, state) {
4
+ super(message);
5
+ this.state = state;
6
+ }
7
+ }
8
+ export default InvalidState;
@@ -0,0 +1,10 @@
1
+ class InvalidStateTransition extends Error {
2
+ state;
3
+ action;
4
+ constructor(message, action, state) {
5
+ super(message);
6
+ this.action = action;
7
+ this.state = state;
8
+ }
9
+ }
10
+ export default InvalidStateTransition;
@@ -3,6 +3,8 @@ import { Invitation } from 'sip.js/lib/api';
3
3
  import CallSession from '../CallSession';
4
4
  import Emitter from '../../utils/Emitter';
5
5
  import IssueReporter from '../../service/IssueReporter';
6
+ import { getSipSessionId } from '../../utils/sdp';
7
+ import { downgradeToAudio, getStreamFromConstraints, getWebRtcStats, setLocalMediaStream, upgradeToVideo } from '../../utils/webrtc';
6
8
  export const ON_USER_AGENT = 'onUserAgent';
7
9
  export const ON_REGISTERED = 'onRegistered';
8
10
  export const ON_UNREGISTERED = 'onUnRegistered';
@@ -219,7 +221,7 @@ export default class WebRTCPhone extends Emitter {
219
221
  id: callSession ? callSession.getId() : null,
220
222
  withMessage,
221
223
  });
222
- this.client.downgradeToAudio(sipSession);
224
+ downgradeToAudio(sipSession);
223
225
  if (callSession) {
224
226
  callSession.cameraEnabled = false;
225
227
  this._updateCallSession(callSession);
@@ -244,7 +246,7 @@ export default class WebRTCPhone extends Emitter {
244
246
  const desktop = constraints && constraints.desktop;
245
247
  const options = sipSession.sessionDescriptionHandlerOptionsReInvite;
246
248
  const wasAudioOnly = options?.audioOnly;
247
- const newStream = await this.client.upgradeToVideo(sipSession, constraints, isConference);
249
+ const newStream = await upgradeToVideo(sipSession, constraints, isConference);
248
250
  if (callSession) {
249
251
  callSession.cameraEnabled = true;
250
252
  this._updateCallSession(callSession);
@@ -277,16 +279,15 @@ export default class WebRTCPhone extends Emitter {
277
279
  content: {
278
280
  type: ON_MESSAGE_TRACK_UPDATED,
279
281
  update: isUpgrade ? 'upgrade' : 'downgrade',
280
- sipCallId: sipSession && this.getSipSessionId(sipSession),
282
+ sipCallId: sipSession && getSipSessionId(sipSession),
281
283
  callId: callSession ? callSession.callId : null,
282
284
  number: callSession ? callSession.number : null,
283
- callerNumber: callSession ? callSession.callerNumber : null,
284
285
  },
285
286
  }));
286
287
  }, 2500);
287
288
  }
288
289
  _bindEvents(sipSession) {
289
- const sipSessionId = this.getSipSessionId(sipSession);
290
+ const sipSessionId = getSipSessionId(sipSession);
290
291
  if (sipSession instanceof Invitation) {
291
292
  // Monkey patch to know when canceled with the CANCEL message
292
293
  const onCancel = sipSession._onCancel.bind(sipSession);
@@ -340,7 +341,7 @@ export default class WebRTCPhone extends Emitter {
340
341
  const [stream] = event.streams;
341
342
  const kind = event && event.track && event.track.kind;
342
343
  logger.info('on track stream called on the peer connection', {
343
- callId: this.getSipSessionId(sipSession),
344
+ callId: getSipSessionId(sipSession),
344
345
  streamId: stream ? stream.id : null,
345
346
  tracks: stream ? stream.getTracks() : null,
346
347
  kind,
@@ -363,15 +364,15 @@ export default class WebRTCPhone extends Emitter {
363
364
  constraints,
364
365
  id: callSession ? callSession.getId() : null,
365
366
  });
366
- const screenShareStream = await this.client.getStreamFromConstraints(constraints);
367
+ const screenShareStream = await getStreamFromConstraints(constraints);
367
368
  if (!screenShareStream) {
368
369
  throw new Error(`Can't create media stream for screensharing with: ${JSON.stringify(constraints)}`);
369
370
  }
370
371
  const screenTrack = screenShareStream.getVideoTracks()[0];
371
372
  const sipSession = this.currentSipSession;
372
- const pc = sipSession && this.client.getPeerConnection(this.getSipSessionId(sipSession));
373
+ const pc = sipSession && this.client.getPeerConnection(getSipSessionId(sipSession));
373
374
  const sender = pc && pc.getSenders && pc.getSenders().find((s) => s && s.track && s.track.kind === 'video');
374
- const localStream = sipSession && this.client.getLocalStream(this.getSipSessionId(sipSession));
375
+ const localStream = sipSession && this.client.getLocalStream(getSipSessionId(sipSession));
375
376
  const videoTrack = localStream ? localStream.getTracks().find(track => track.kind === 'video') : null;
376
377
  const hadVideo = !!videoTrack;
377
378
  // Stop local video tracks
@@ -433,19 +434,19 @@ export default class WebRTCPhone extends Emitter {
433
434
  }
434
435
  _onScreenSharing(screenStream, sipSession, callSession, hadVideo, desktop) {
435
436
  const screenTrack = screenStream.getVideoTracks()[0];
436
- const sipSessionId = this.getSipSessionId(sipSession);
437
+ const sipSessionId = getSipSessionId(sipSession);
437
438
  const pc = this.client.getPeerConnection(sipSessionId);
438
439
  const sender = pc && pc.getSenders && pc.getSenders().find((s) => s && s.track && s.track.kind === 'video');
439
440
  logger.info('WebRTC phone - on screensharing', {
440
441
  hadVideo,
441
- id: this.getSipSessionId(sipSession),
442
+ id: getSipSessionId(sipSession),
442
443
  screenTrack,
443
444
  });
444
445
  if (screenTrack) {
445
446
  screenTrack.onended = () => {
446
447
  logger.info('WebRTC phone - on screenshare ended', {
447
448
  hadVideo,
448
- id: this.getSipSessionId(sipSession),
449
+ id: getSipSessionId(sipSession),
449
450
  screenTrack,
450
451
  });
451
452
  this.eventEmitter.emit(ON_SHARE_SCREEN_ENDING, this._createCallSession(sipSession, callSession));
@@ -458,7 +459,7 @@ export default class WebRTCPhone extends Emitter {
458
459
  sipSessionId,
459
460
  desktop,
460
461
  };
461
- this.client.setLocalMediaStream(this.getSipSessionId(sipSession), screenStream);
462
+ setLocalMediaStream(sipSession, screenStream);
462
463
  this.eventEmitter.emit(ON_SHARE_SCREEN_STARTED, this._createCallSession(sipSession, callSession, {
463
464
  screensharing: true,
464
465
  }), screenStream);
@@ -479,7 +480,7 @@ export default class WebRTCPhone extends Emitter {
479
480
  this.currentSipSession = sipSession;
480
481
  this.currentCallSession = callSession;
481
482
  this.eventEmitter.emit(ON_TERMINATE_SOUND, callSession, 'call accepted');
482
- const sipSessionId = this.getSipSessionId(sipSession);
483
+ const sipSessionId = getSipSessionId(sipSession);
483
484
  if (sipSessionId) {
484
485
  this.removeIncomingSessions(sipSessionId);
485
486
  }
@@ -538,7 +539,7 @@ export default class WebRTCPhone extends Emitter {
538
539
  return this.currentSipSession && this.client.changeSessionVideoInputDevice(id, this.currentSipSession);
539
540
  }
540
541
  _onCallTerminated(sipSession) {
541
- const sipSessionId = this.getSipSessionId(sipSession);
542
+ const sipSessionId = getSipSessionId(sipSession);
542
543
  logger.info('WebRTC phone - on call terminated', {
543
544
  sipId: sipSession.id,
544
545
  sipSessionId,
@@ -630,7 +631,7 @@ export default class WebRTCPhone extends Emitter {
630
631
  if (!this.currentSipSession) {
631
632
  return false;
632
633
  }
633
- return this.currentSipSession && this.getSipSessionId(this.currentSipSession) === callSession.getId();
634
+ return this.currentSipSession && getSipSessionId(this.currentSipSession) === callSession.getId();
634
635
  }
635
636
  hasVideo(callSession) {
636
637
  return this.client.hasVideo(callSession.getId());
@@ -783,7 +784,7 @@ export default class WebRTCPhone extends Emitter {
783
784
  if (!sipSession) {
784
785
  return new Promise((resolve, reject) => reject(new Error('No session to hold')));
785
786
  }
786
- const sessionId = this.getSipSessionId(sipSession);
787
+ const sessionId = getSipSessionId(sipSession);
787
788
  const hasVideo = this.client.hasLocalVideo(sessionId);
788
789
  logger.info('WebRTC hold sip session', {
789
790
  sipId: sipSession.id,
@@ -796,7 +797,7 @@ export default class WebRTCPhone extends Emitter {
796
797
  }
797
798
  // Downgrade to audio if needed
798
799
  if (hasVideo) {
799
- await this.client.downgradeToAudio(sipSession);
800
+ await downgradeToAudio(sipSession);
800
801
  }
801
802
  const isConference = !!callSession && callSession.isConference();
802
803
  const promise = this.client.hold(sipSession, isConference, hasVideo);
@@ -810,7 +811,7 @@ export default class WebRTCPhone extends Emitter {
810
811
  return new Promise((resolve, reject) => reject(new Error('No session to unhold')));
811
812
  }
812
813
  const isConference = !!callSession && callSession.isConference();
813
- const sessionId = this.getSipSessionId(sipSession);
814
+ const sessionId = getSipSessionId(sipSession);
814
815
  logger.info('WebRTC unhold sip session', {
815
816
  sessionId,
816
817
  isConference,
@@ -826,7 +827,7 @@ export default class WebRTCPhone extends Emitter {
826
827
  screen: wasScreensharing,
827
828
  desktop: wasDesktop,
828
829
  };
829
- await this.client.upgradeToVideo(sipSession, constraints, isConference);
830
+ await upgradeToVideo(sipSession, constraints, isConference);
830
831
  }
831
832
  const onScreenSharing = (stream) => {
832
833
  const hadVideo = this.lastScreenShare && this.lastScreenShare.hadVideo;
@@ -1033,7 +1034,7 @@ export default class WebRTCPhone extends Emitter {
1033
1034
  console.error('Call is unknown to the WebRTC phone', callSession ? callSession.sipCallId : null, callSession ? callSession.callId : null);
1034
1035
  return false;
1035
1036
  }
1036
- const sipSessionId = this.getSipSessionId(sipSession);
1037
+ const sipSessionId = getSipSessionId(sipSession);
1037
1038
  logger.info('WebRTC hangup', {
1038
1039
  sipId: sipSession.id,
1039
1040
  sipSessionId,
@@ -1051,7 +1052,7 @@ export default class WebRTCPhone extends Emitter {
1051
1052
  }
1052
1053
  async getStats(callSession) {
1053
1054
  const sipSession = this.findSipSession(callSession);
1054
- return sipSession ? this.client.getStats(sipSession) : null;
1055
+ return sipSession ? getWebRtcStats(sipSession) : null;
1055
1056
  }
1056
1057
  startNetworkMonitoring(callSession, interval = 1000) {
1057
1058
  const sipSession = this.findSipSession(callSession);
@@ -1220,12 +1221,12 @@ export default class WebRTCPhone extends Emitter {
1220
1221
  // if `_createCallSession` is called too soon `hasVideo` will return false because the stream doesn't exists yet
1221
1222
  setTimeout(() => {
1222
1223
  // Update callSession
1223
- this._createCallSession(sipSession, this.callSessions[this.getSipSessionId(sipSession)]);
1224
+ this._createCallSession(sipSession, this.callSessions[getSipSessionId(sipSession)]);
1224
1225
  this.eventEmitter.emit.apply(this.eventEmitter, [this.client.ON_REINVITE, ...args]);
1225
1226
  }, 2000);
1226
1227
  });
1227
1228
  this.client.on(this.client.ACCEPTED, async (sipSession) => {
1228
- const sessionId = this.getSipSessionId(sipSession);
1229
+ const sessionId = getSipSessionId(sipSession);
1229
1230
  const hasSession = (sessionId in this.callSessions);
1230
1231
  logger.info('WebRTC call accepted', {
1231
1232
  sipId: sessionId,
@@ -1252,7 +1253,7 @@ export default class WebRTCPhone extends Emitter {
1252
1253
  const callSession = this._createCallSession(session);
1253
1254
  // `_createCallSession` add the CallSession in `this.callSessions`.
1254
1255
  // so we have to remove it or `callCount` will always return 1 even if the call was rejected
1255
- delete this.callSessions[this.getSipSessionId(session)];
1256
+ delete this.callSessions[getSipSessionId(session)];
1256
1257
  callSession.endTime = new Date();
1257
1258
  this.eventEmitter.emit(ON_CALL_REJECTED, callSession);
1258
1259
  });
@@ -1269,7 +1270,7 @@ export default class WebRTCPhone extends Emitter {
1269
1270
  const callSession = this._createCallSession(session, null, {
1270
1271
  incoming: false,
1271
1272
  ringing: true,
1272
- callId: this.getSipSessionId(session),
1273
+ callId: getSipSessionId(session),
1273
1274
  });
1274
1275
  this.eventEmitter.emit(ON_PROGRESS, callSession, this.audioOutputDeviceId, this.audioOutputVolume);
1275
1276
  });
@@ -1278,7 +1279,7 @@ export default class WebRTCPhone extends Emitter {
1278
1279
  const callSession = this._createCallSession(session, null, {
1279
1280
  incoming: false,
1280
1281
  ringing: true,
1281
- callId: this.getSipSessionId(session),
1282
+ callId: getSipSessionId(session),
1282
1283
  });
1283
1284
  this.eventEmitter.emit(ON_EARLY_MEDIA, callSession, this.audioOutputDeviceId, this.audioOutputVolume);
1284
1285
  });
@@ -1334,12 +1335,12 @@ export default class WebRTCPhone extends Emitter {
1334
1335
  this.eventEmitter.emit(ON_MESSAGE, message);
1335
1336
  });
1336
1337
  this.client.on(this.client.ON_NETWORK_STATS, (session, stats, previousStats) => {
1337
- const callSession = this.getCallSession(this.getSipSessionId(session));
1338
+ const callSession = this.getCallSession(getSipSessionId(session));
1338
1339
  this.eventEmitter.emit(ON_NETWORK_STATS, callSession, stats, previousStats);
1339
1340
  });
1340
1341
  // Used when upgrading directly in screenshare mode
1341
1342
  this.client.on(this.client.ON_SCREEN_SHARING_REINVITE, (sipSession, response, desktop) => {
1342
- const sipSessionId = this.getSipSessionId(sipSession);
1343
+ const sipSessionId = getSipSessionId(sipSession);
1343
1344
  const localStream = this.client.getLocalStream(sipSessionId);
1344
1345
  const callSession = this.callSessions[sipSessionId];
1345
1346
  logger.info('Updrading directly in screensharing mode', {
@@ -1354,7 +1355,7 @@ export default class WebRTCPhone extends Emitter {
1354
1355
  const keys = this.client.getSipSessionIds();
1355
1356
  const keyIndex = keys.findIndex(sessionId => callSession && callSession.isId(sessionId));
1356
1357
  if (keyIndex === -1) {
1357
- const currentSipSessionId = this.currentSipSession ? this.getSipSessionId(this.currentSipSession) : this.client.getSipSessionIds()[0];
1358
+ const currentSipSessionId = this.currentSipSession ? getSipSessionId(this.currentSipSession) : this.client.getSipSessionIds()[0];
1358
1359
  return currentSipSessionId ? this.client.getSipSession(currentSipSessionId) : null;
1359
1360
  }
1360
1361
  return this.client.getSipSession(keys[keyIndex]);
@@ -1362,12 +1363,9 @@ export default class WebRTCPhone extends Emitter {
1362
1363
  getCallSession(sipSessionId) {
1363
1364
  return this.callSessions[sipSessionId];
1364
1365
  }
1365
- getSipSessionId(sipSession) {
1366
- return this.client.getSipSessionId(sipSession);
1367
- }
1368
1366
  getSipSessionIdFromCallSession(callSession) {
1369
1367
  const sipSession = this.findSipSession(callSession);
1370
- return sipSession ? this.getSipSessionId(sipSession) : null;
1368
+ return sipSession ? getSipSessionId(sipSession) : null;
1371
1369
  }
1372
1370
  _updateCallSession(callSession) {
1373
1371
  const sipSessionId = this.getSipSessionIdFromCallSession(callSession);
@@ -1445,7 +1443,7 @@ export default class WebRTCPhone extends Emitter {
1445
1443
  // @ts-ignore: private
1446
1444
  const number = identity ? identity.uri._normal.user : null;
1447
1445
  const { state, } = sipSession || {};
1448
- const sessionId = this.getSipSessionId(sipSession);
1446
+ const sessionId = getSipSessionId(sipSession);
1449
1447
  fromSession = fromSession || this.callSessions[sessionId];
1450
1448
  const callSession = new CallSession({
1451
1449
  callId: fromSession && fromSession.callId,
@@ -122,19 +122,25 @@ export class Auth {
122
122
  return this._onAuthenticated(rawSession);
123
123
  }
124
124
  async initiateIdpAuthentication(domain, redirectUrl) {
125
+ let response;
125
126
  try {
126
- return await getApiClient().auth.initiateIdpAuthentication(domain, redirectUrl);
127
+ response = await getApiClient().auth.initiateIdpAuthentication(domain, redirectUrl);
127
128
  }
128
129
  catch (e) {
129
- logger.error('Error during IdP authentication initialization:', e.message);
130
- if (e.status === 404) {
131
- throw new NoSamlRouteError('No route found for SAML SSO');
130
+ logger.error('Error during IdP authentication initiation:', e.message);
131
+ return;
132
+ }
133
+ if (!response.ok) {
134
+ if (response.status === 404) {
135
+ throw new NoSamlRouteError('No route found for saml sso');
132
136
  }
133
- if (e.status === 500) {
137
+ if (response.status === 500) {
134
138
  throw new SamlConfigError('SAML/server configuration error');
135
139
  }
136
- throw e;
140
+ const error = await response.json();
141
+ throw new Error(error.message || error.reason);
137
142
  }
143
+ return response.json();
138
144
  }
139
145
  async logInViaRefreshToken(refreshToken) {
140
146
  const rawSession = await getApiClient().auth.refreshToken(refreshToken, '', this.expiration, this.mobile);
@@ -7,6 +7,7 @@ import IssueReporter from '../service/IssueReporter';
7
7
  import Emitter from '../utils/Emitter';
8
8
  import Wazo from './index';
9
9
  import SFUNotAvailableError from '../domain/SFUNotAvailableError';
10
+ import { getSipSessionId } from '../utils/sdp';
10
11
  const logger = IssueReporter.loggerFor('simple-phone');
11
12
  const sipLogger = IssueReporter.loggerFor('sip.js');
12
13
  const protocolLogger = IssueReporter.loggerFor('sip');
@@ -230,7 +231,7 @@ export class Phone extends Emitter {
230
231
  if (!sipSession || !this.phone) {
231
232
  return null;
232
233
  }
233
- return this.phone.getSipSessionId(sipSession);
234
+ return getSipSessionId(sipSession);
234
235
  }
235
236
  sendMessage(body, sipSession, contentType = 'text/plain') {
236
237
  const toSipSession = sipSession || this.getCurrentSipSession();
@@ -1,5 +1,3 @@
1
- import BadResponse from '../../domain/BadResponse';
2
- import ServerError from '../../domain/ServerError';
3
1
  import getApiClient, { setRefreshToken } from '../../service/getApiClient';
4
2
  import { Auth as AuthClass } from '../Auth';
5
3
  const Auth = new AuthClass();
@@ -112,8 +110,12 @@ describe('simple/Auth', () => {
112
110
  });
113
111
  describe('initiateIdpAuthentication', () => {
114
112
  const validResponse = { location: 'idp.com/auth', saml_session_id: 'a1b2C3d4' };
115
- it('should return a `location` and `saml_session_id` if the response is valid', async () => {
116
- const initiateIdpAuthentication = jest.fn(() => validResponse);
113
+ it('should return a `location` and `saml_session_id` if the response is ok', async () => {
114
+ const initiateIdpAuthentication = jest.fn(() => Promise.resolve({
115
+ ok: true,
116
+ status: 303,
117
+ json: () => Promise.resolve(validResponse),
118
+ }));
117
119
  getApiClient.mockImplementation(() => ({
118
120
  auth: {
119
121
  initiateIdpAuthentication,
@@ -123,16 +125,70 @@ describe('simple/Auth', () => {
123
125
  expect(response).toBe(validResponse);
124
126
  });
125
127
  it('should return a `NoSamlRouteError` if the response is a 404', async () => {
126
- const initiateIdpAuthentication = jest.fn(() => { throw BadResponse.fromText('an error message', 404); });
128
+ const initiateIdpAuthentication = jest.fn(() => Promise.resolve({
129
+ ok: false,
130
+ status: 404,
131
+ }));
132
+ getApiClient.mockImplementation(() => ({
133
+ auth: {
134
+ initiateIdpAuthentication,
135
+ },
136
+ }));
137
+ await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow('No route found for saml sso');
138
+ });
139
+ it('should return an error `message` if the response is NOT ok', async () => {
140
+ const initiateIdpAuthentication = jest.fn(() => Promise.resolve({
141
+ ok: false,
142
+ json: () => Promise.resolve({ message: 'an error message', reason: 'an error reason' }),
143
+ }));
144
+ getApiClient.mockImplementation(() => ({
145
+ auth: {
146
+ initiateIdpAuthentication,
147
+ },
148
+ }));
149
+ await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow('an error message');
150
+ });
151
+ it('should return an error `reason` if there is no `message` and the response is NOT ok', async () => {
152
+ const initiateIdpAuthentication = jest.fn(() => Promise.resolve({
153
+ ok: false,
154
+ json: () => Promise.resolve({ reason: 'an error reason' }),
155
+ }));
127
156
  getApiClient.mockImplementation(() => ({
128
157
  auth: {
129
158
  initiateIdpAuthentication,
130
159
  },
131
160
  }));
132
- await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow('No route found for SAML SSO');
161
+ await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow('an error reason');
133
162
  });
134
- it('should return a `SamlConfigError` if the response is a 500', async () => {
135
- const initiateIdpAuthentication = jest.fn(() => { throw ServerError.fromText('an error message', 500); });
163
+ it('should return an error with a null message if there is no `message` and `reason`', async () => {
164
+ const initiateIdpAuthentication = jest.fn(() => Promise.resolve({
165
+ ok: false,
166
+ json: () => Promise.resolve({}),
167
+ }));
168
+ getApiClient.mockImplementation(() => ({
169
+ auth: {
170
+ initiateIdpAuthentication,
171
+ },
172
+ }));
173
+ await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow('');
174
+ });
175
+ it('should return a NoSAMLRouteError if the response is a 404', async () => {
176
+ const initiateIdpAuthentication = jest.fn(() => Promise.resolve({
177
+ ok: false,
178
+ status: 404,
179
+ }));
180
+ getApiClient.mockImplementation(() => ({
181
+ auth: {
182
+ initiateIdpAuthentication,
183
+ },
184
+ }));
185
+ await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow('No route found for saml sso');
186
+ });
187
+ it('should return a SamlConfigError if the response is a 500', async () => {
188
+ const initiateIdpAuthentication = jest.fn(() => Promise.resolve({
189
+ ok: false,
190
+ status: 500,
191
+ }));
136
192
  getApiClient.mockImplementation(() => ({
137
193
  auth: {
138
194
  initiateIdpAuthentication,
@@ -17,7 +17,7 @@ import { CanceledCallError } from '../web-rtc-client';
17
17
  import BadResponse from '../domain/BadResponse';
18
18
  import ServerError from '../domain/ServerError';
19
19
  import SFUNotAvailableError from '../domain/SFUNotAvailableError';
20
- import Call from '../domain/Call';
20
+ import ApiCall from '../domain/Call';
21
21
  import CallLog from '../domain/CallLog';
22
22
  import Recording from '../domain/Recording';
23
23
  import ChatMessage from '../domain/ChatMessage';
@@ -40,6 +40,8 @@ import IssueReporter from '../service/IssueReporter';
40
40
  import Features from '../domain/Features';
41
41
  import Meeting from '../domain/Meeting';
42
42
  import Checker from '../checker/Checker';
43
+ import Softphone from '../voice/softphone';
44
+ import Call from '../voice/call';
43
45
  const Wazo = {
44
46
  Auth,
45
47
  Phone,
@@ -59,11 +61,15 @@ const Wazo = {
59
61
  loggerFor: IssueReporter.loggerFor.bind(IssueReporter),
60
62
  Features,
61
63
  Checker,
64
+ Voice: {
65
+ Softphone,
66
+ Call,
67
+ },
62
68
  // Domain
63
69
  domain: {
64
70
  BadResponse,
65
71
  ServerError,
66
- Call,
72
+ Call: ApiCall,
67
73
  CallLog,
68
74
  CTIPhone,
69
75
  Recording,