@wazo/sdk 1.0.0-alpha.1 → 1.0.0-alpha.3

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 (73) hide show
  1. package/dist/wazo-sdk.js +6 -6
  2. package/esm/__tests__/__voice__/call.test.js +45 -2
  3. package/esm/domain/Phone/CTIPhone.js +2 -2
  4. package/esm/domain/Phone/WebRTCPhone.js +6 -7
  5. package/esm/index.js +1 -1
  6. package/esm/service/CallApi.js +4 -4
  7. package/esm/simple/Phone.js +1 -0
  8. package/esm/state-machine/call-state-machine.js +1 -1
  9. package/esm/types/domain/Phone/CTIPhone.d.ts.map +1 -1
  10. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  11. package/esm/types/index.d.ts +1 -1
  12. package/esm/types/index.d.ts.map +1 -1
  13. package/esm/types/service/CallApi.d.ts +5 -5
  14. package/esm/types/service/CallApi.d.ts.map +1 -1
  15. package/esm/types/simple/Phone.d.ts.map +1 -1
  16. package/esm/types/state-machine/softphone-state-machine.d.ts +1 -1
  17. package/esm/types/state-machine/utils.d.ts +1 -1
  18. package/esm/types/state-machine/utils.d.ts.map +1 -1
  19. package/esm/types/utils/sdp.d.ts +2 -0
  20. package/esm/types/utils/sdp.d.ts.map +1 -1
  21. package/esm/types/utils/webrtc.d.ts +1 -0
  22. package/esm/types/utils/webrtc.d.ts.map +1 -1
  23. package/esm/types/voice/call.d.ts +27 -12
  24. package/esm/types/voice/call.d.ts.map +1 -1
  25. package/esm/types/voice/softphone.d.ts +22 -13
  26. package/esm/types/voice/softphone.d.ts.map +1 -1
  27. package/esm/utils/sdp.js +13 -0
  28. package/esm/utils/webrtc.js +20 -0
  29. package/esm/voice/call.js +115 -33
  30. package/esm/voice/softphone.js +105 -25
  31. package/lib/__tests__/__voice__/call.test.js +44 -1
  32. package/lib/domain/Phone/CTIPhone.js +2 -2
  33. package/lib/domain/Phone/WebRTCPhone.js +5 -6
  34. package/lib/index.js +2 -2
  35. package/lib/service/CallApi.js +2 -2
  36. package/lib/simple/Phone.js +1 -0
  37. package/lib/state-machine/call-state-machine.js +1 -1
  38. package/lib/types/domain/Phone/CTIPhone.d.ts.map +1 -1
  39. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  40. package/lib/types/index.d.ts +1 -1
  41. package/lib/types/index.d.ts.map +1 -1
  42. package/lib/types/service/CallApi.d.ts +5 -5
  43. package/lib/types/service/CallApi.d.ts.map +1 -1
  44. package/lib/types/simple/Phone.d.ts.map +1 -1
  45. package/lib/types/state-machine/softphone-state-machine.d.ts +1 -1
  46. package/lib/types/state-machine/utils.d.ts +1 -1
  47. package/lib/types/state-machine/utils.d.ts.map +1 -1
  48. package/lib/types/utils/sdp.d.ts +2 -0
  49. package/lib/types/utils/sdp.d.ts.map +1 -1
  50. package/lib/types/utils/webrtc.d.ts +1 -0
  51. package/lib/types/utils/webrtc.d.ts.map +1 -1
  52. package/lib/types/voice/call.d.ts +27 -12
  53. package/lib/types/voice/call.d.ts.map +1 -1
  54. package/lib/types/voice/softphone.d.ts +22 -13
  55. package/lib/types/voice/softphone.d.ts.map +1 -1
  56. package/lib/utils/sdp.js +16 -1
  57. package/lib/utils/webrtc.js +22 -1
  58. package/lib/voice/call.js +112 -31
  59. package/lib/voice/softphone.js +109 -25
  60. package/package.json +1 -1
  61. package/src/__tests__/__voice__/call.test.ts +48 -2
  62. package/src/domain/Phone/CTIPhone.ts +3 -2
  63. package/src/domain/Phone/WebRTCPhone.ts +9 -9
  64. package/src/index.ts +1 -1
  65. package/src/service/CallApi.ts +7 -7
  66. package/src/simple/Phone.ts +1 -0
  67. package/src/state-machine/call-state-machine.ts +1 -1
  68. package/src/state-machine/utils.ts +2 -2
  69. package/src/utils/sdp.ts +16 -0
  70. package/src/utils/webrtc.ts +23 -1
  71. package/src/voice/call.ts +149 -43
  72. package/src/voice/softphone.ts +130 -31
  73. package/src/web-rtc-client.ts +1 -1
@@ -3,8 +3,9 @@ import { Inviter, UserAgent, type URI } from 'sip.js';
3
3
 
4
4
  import softphone from '../../voice/softphone';
5
5
  import Call from '../../voice/call';
6
- import { Actions, States } from '../../state-machine/call-state-machine';
6
+ import { Actions, EstablishedStates, States } from '../../state-machine/call-state-machine';
7
7
  import { Actions as SoftphoneActions } from '../../state-machine/softphone-state-machine';
8
+ import ApiCall from '../../domain/Call';
8
9
 
9
10
  import InvalidStateTransition from '../../domain/InvalidStateTransition';
10
11
 
@@ -33,6 +34,7 @@ jest.mock('../../web-rtc-client', () => jest.fn().mockImplementation(() => ({
33
34
  answer: jest.fn(() => Promise.resolve()),
34
35
  hangup: jest.fn(() => Promise.resolve()),
35
36
  register: jest.fn(() => Promise.resolve()),
37
+ onCallEnded: jest.fn(() => Promise.resolve()),
36
38
  setOnHeartbeatTimeout: jest.fn(() => Promise.resolve()),
37
39
  setOnHeartbeatCallback: jest.fn(() => Promise.resolve()),
38
40
  on: jest.fn(),
@@ -42,11 +44,45 @@ const ua = new UserAgent(uaOptions);
42
44
  const sipCall = new Inviter(ua, UserAgent.makeURI('sip:1234@my.stack.io') as URI);
43
45
 
44
46
  softphone.connect();
47
+ // @ts-ignore: private method
45
48
  softphone._sendAction(SoftphoneActions.REGISTER_DONE);
46
49
 
47
50
  const invalidStateTransition = (state: string, action: string) => new InvalidStateTransition(`Invalid state transition from ${state} with action ${action}`, state, action);
48
51
 
49
52
  describe('Call', () => {
53
+ describe('parsing an API call', () => {
54
+ it('Should parse an API call and transform it to a Call instance', async () => {
55
+ const apiCall = new ApiCall({
56
+ id: '1234',
57
+ sipCallId: 'abcd',
58
+ isCaller: false,
59
+ isVideo: true,
60
+ recording: true,
61
+ callerName: '',
62
+ callerNumber: '',
63
+ calleeName: '',
64
+ calleeNumber: '0123423223',
65
+ dialedExtension: '',
66
+ lineId: null,
67
+ muted: true,
68
+ onHold: false,
69
+ startingTime: new Date(),
70
+ status: 'Up',
71
+ talkingToIds: [],
72
+ });
73
+ const call = Call.parseCall(apiCall);
74
+
75
+ expect(call.recording).toBeTruthy();
76
+ expect(call.id).toBe(apiCall.sipCallId);
77
+ expect(call.apiId).toBe(apiCall.id);
78
+ expect(call.number).toBe(apiCall.calleeNumber);
79
+ expect(call.answerTime).toStrictEqual(apiCall.startingTime);
80
+ expect(call.isMuted()).toBeTruthy();
81
+ expect(call.state).toStrictEqual({ [States.ESTABLISHED]: EstablishedStates.MUTED });
82
+ expect(call.isEstablished()).toBeTruthy();
83
+ });
84
+ });
85
+
50
86
  describe('accept', () => {
51
87
  it('Should throw an error when accepting a not ringing call', async () => {
52
88
  const call = new Call(sipCall, softphone);
@@ -54,16 +90,24 @@ describe('Call', () => {
54
90
  await expect(call.accept()).rejects.toThrowError(invalidStateTransition(States.IDLE, Actions.ACCEPT));
55
91
  });
56
92
 
57
- it.only('Should throw an error when the softphone is not registered', async () => {
93
+ it('Should throw an error when the softphone is not registered', async () => {
58
94
  const call = new Call(sipCall, softphone);
95
+ // @ts-ignore: private method
59
96
  call._sendAction(Actions.INCOMING_CALL);
97
+ // @ts-ignore: private method
60
98
  softphone._sendAction(SoftphoneActions.TRANSPORT_CLOSED);
61
99
 
62
100
  await expect(call.accept()).rejects.toThrowError(invalidStateTransition(States.RINGING, Actions.ACCEPT));
101
+
102
+ // Revert softphone state
103
+ softphone.connect();
104
+ // @ts-ignore: private method
105
+ softphone._sendAction(SoftphoneActions.REGISTER_DONE);
63
106
  });
64
107
 
65
108
  it('Should not throw an error when accepting a not ringing call', async () => {
66
109
  const call = new Call(sipCall, softphone);
110
+ // @ts-ignore: private method
67
111
  call._sendAction(Actions.INCOMING_CALL);
68
112
 
69
113
  await call.accept();
@@ -80,7 +124,9 @@ describe('Call', () => {
80
124
 
81
125
  it('Should not throw an error when terminating an established call', async () => {
82
126
  const call = new Call(sipCall, softphone);
127
+ // @ts-ignore: private method
83
128
  call._sendAction(Actions.MAKE_CALL);
129
+ // @ts-ignore: private method
84
130
  call._sendAction(Actions.REMOTLY_ACCEPTED);
85
131
 
86
132
  await call.hangup();
@@ -1,5 +1,6 @@
1
1
  import Session from '../Session';
2
2
  import Call from '../Call';
3
+ import SoftphoneCall from '../../voice/call';
3
4
  import Line from '../Line';
4
5
  import CallSession from '../CallSession';
5
6
  import type { Phone, AvailablePhoneOptions } from './Phone';
@@ -141,7 +142,7 @@ export default class CTIPhone extends Emitter implements Phone {
141
142
  });
142
143
 
143
144
  try {
144
- await CallApi.cancelCall(callSession);
145
+ await CallApi.cancelCall({ apiId: callSession.getId() } as SoftphoneCall);
145
146
 
146
147
  if (this.currentCall && callSession.callId === this.currentCall.id) {
147
148
  this.endCurrentCall(callSession);
@@ -167,7 +168,7 @@ export default class CTIPhone extends Emitter implements Phone {
167
168
  callId: callSession.getId(),
168
169
  number: callSession.number,
169
170
  });
170
- await CallApi.cancelCall(callSession);
171
+ await CallApi.cancelCall({ id: callSession.getId() } as SoftphoneCall);
171
172
  this.eventEmitter.emit('onCallEnded', callSession);
172
173
  }
173
174
 
@@ -12,7 +12,7 @@ import WazoWebRTCClient from '../../web-rtc-client';
12
12
  import Emitter from '../../utils/Emitter';
13
13
  import IssueReporter from '../../service/IssueReporter';
14
14
  import { PeerConnection, WazoSession } from '../types';
15
- import { getSipSessionId } from '../../utils/sdp';
15
+ import { getCallDisplayName, getCallNumber, getSipSessionId } from '../../utils/sdp';
16
16
  import { downgradeToAudio, getStreamFromConstraints, getWebRtcStats, setLocalMediaStream, upgradeToVideo } from '../../utils/webrtc';
17
17
 
18
18
  export const ON_USER_AGENT = 'onUserAgent';
@@ -61,6 +61,8 @@ export const MESSAGE_TYPE_CHAT = 'message/TYPE_CHAT';
61
61
  export const MESSAGE_TYPE_SIGNAL = 'message/TYPE_SIGNAL';
62
62
  export const events = [ON_USER_AGENT, ON_REGISTERED, ON_UNREGISTERED, ON_PROGRESS, ON_CALL_ACCEPTED, ON_CALL_ANSWERED, ON_CALL_INCOMING, ON_CALL_OUTGOING, ON_CALL_MUTED, ON_CALL_UNMUTED, ON_CALL_RESUMED, ON_CALL_HELD, ON_CALL_UNHELD, ON_CAMERA_DISABLED, ON_CALL_FAILED, ON_CALL_ENDED, ON_CALL_REJECTED, ON_MESSAGE, ON_REINVITE, ON_TRACK, ON_AUDIO_STREAM, ON_VIDEO_STREAM, ON_REMOVE_STREAM, ON_SHARE_SCREEN_ENDED, ON_TERMINATE_SOUND, ON_PLAY_RING_SOUND, ON_PLAY_INBOUND_CALL_SIGNAL_SOUND, ON_PLAY_HANGUP_SOUND, ON_PLAY_PROGRESS_SOUND, ON_VIDEO_INPUT_CHANGE, ON_SHARE_SCREEN_STARTED, ON_CALL_ERROR, ON_CHAT, ON_SIGNAL, ON_NETWORK_STATS, ON_DISCONNECTED, ON_EARLY_MEDIA];
63
63
  const logger = IssueReporter.loggerFor('webrtc-phone');
64
+
65
+ // @deprecated: Use Wazo.Voice.Softphone instead
64
66
  export default class WebRTCPhone extends Emitter implements Phone {
65
67
  client: WazoWebRTCClient;
66
68
 
@@ -1783,20 +1785,17 @@ export default class WebRTCPhone extends Emitter implements Phone {
1783
1785
  }
1784
1786
 
1785
1787
  _createCallSession(sipSession: WazoSession, fromSession?: CallSession | null | undefined, extra: Record<string, any> = {}): CallSession {
1786
- // eslint-disable-next-line
1787
- const identity = sipSession ? sipSession.remoteIdentity || sipSession.assertedIdentity : null;
1788
- // @ts-ignore: private
1789
- const number = identity ? identity.uri._normal.user : null;
1790
- const {
1791
- state,
1792
- } = sipSession || {};
1788
+ const number = getCallNumber(sipSession);
1789
+ const displayName = getCallDisplayName(sipSession);
1790
+ const { state } = sipSession || {};
1793
1791
  const sessionId = getSipSessionId(sipSession);
1792
+
1794
1793
  fromSession = fromSession || this.callSessions[sessionId];
1795
1794
  const callSession = new CallSession({
1796
1795
  callId: fromSession && fromSession.callId,
1797
1796
  sipCallId: sessionId,
1798
1797
  sipStatus: state,
1799
- displayName: identity ? identity.displayName || number : number,
1798
+ displayName,
1800
1799
  // @ts-ignore: date / number
1801
1800
  startTime: fromSession ? fromSession.startTime : new Date(),
1802
1801
  creationTime: fromSession ? fromSession.creationTime : null,
@@ -1817,6 +1816,7 @@ export default class WebRTCPhone extends Emitter implements Phone {
1817
1816
  ...extra,
1818
1817
  });
1819
1818
  this.callSessions[sessionId] = callSession;
1819
+
1820
1820
  return callSession;
1821
1821
  }
1822
1822
 
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ export { default as ServerError } from './domain/ServerError';
7
7
  export { default as InvalidStateTransition } from './domain/InvalidStateTransition';
8
8
  export { default as InvalidState } from './domain/InvalidState';
9
9
  export { default as SFUNotAvailableError } from './domain/SFUNotAvailableError';
10
- export { default as Call } from './domain/Call';
10
+ export { default as ApiCall } from './domain/Call';
11
11
  export { default as CallLog } from './domain/CallLog';
12
12
  export { default as Recording } from './domain/Recording';
13
13
  export { default as ChatMessage } from './domain/ChatMessage';
@@ -1,9 +1,9 @@
1
- import Call from '../domain/Call';
1
+ import ApiCall from '../domain/Call';
2
+ import Call from '../voice/call';
2
3
  import CallLog from '../domain/CallLog';
3
4
  import Line from '../domain/Line';
4
5
  import Session from '../domain/Session';
5
6
  import Relocation from '../domain/Relocation';
6
- import CallSession from '../domain/CallSession';
7
7
  import getApiClient from './getApiClient';
8
8
  import Transfer from '../domain/IndirectTransfer';
9
9
 
@@ -16,7 +16,7 @@ export default class CallApi {
16
16
  return getApiClient().callLogd.listDistinctCallLogs(offset, limit, distinct);
17
17
  }
18
18
 
19
- static async fetchActiveCalls(): Promise<Call[]> {
19
+ static async fetchActiveCalls(): Promise<ApiCall[]> {
20
20
  return getApiClient().calld.listCalls();
21
21
  }
22
22
 
@@ -37,14 +37,14 @@ export default class CallApi {
37
37
  return getApiClient().confd.getUserLineSip(session.uuid as string, lineToUse ? <unknown>lineToUse.id as string : '');
38
38
  }
39
39
 
40
- static async cancelCall(callSession: CallSession): Promise<boolean> {
41
- return getApiClient().calld.cancelCall(callSession.callId);
40
+ static async cancelCall(call: Call): Promise<boolean> {
41
+ return getApiClient().calld.cancelCall(call.apiId || '');
42
42
  }
43
43
 
44
- static async makeCall(callFromLine: Line, extension: string, isMobile = false, callbackAllLines = false): Promise<Call | null | undefined> {
44
+ static async makeCall(callFromLine: Line, extension: string, isMobile = false, callbackAllLines = false): Promise<ApiCall | null | undefined> {
45
45
  const lineId = callFromLine ? callFromLine.id : null;
46
46
  const response = await getApiClient().calld.makeCall(extension, isMobile, lineId, callbackAllLines);
47
- return Call.parse(response);
47
+ return ApiCall.parse(response);
48
48
  }
49
49
 
50
50
  static async relocateCall(callId: string, line: number, contactIdentifier?: string): Promise<Relocation> {
@@ -19,6 +19,7 @@ const sipLogger = IssueReporter.loggerFor('sip.js');
19
19
  const protocolLogger = IssueReporter.loggerFor('sip');
20
20
  const protocolDebugMessages = ['Received WebSocket text message:', 'Sending WebSocket message:'];
21
21
 
22
+ // @deprecated: Use Wazo.Voice.Softphone instead
22
23
  export class Phone extends Emitter {
23
24
  client: WazoWebRTCClient;
24
25
 
@@ -43,7 +43,7 @@ export type EstablishedActionTypes = typeof EstablishedActions[keyof typeof Esta
43
43
 
44
44
  const callMachine = setup({
45
45
  guards: {
46
- isRegistered: () => softphone.getState() === SoftphoneStates.REGISTERED,
46
+ isRegistered: () => softphone.state === SoftphoneStates.REGISTERED,
47
47
  },
48
48
  }).createMachine({
49
49
  id: 'call',
@@ -8,8 +8,8 @@ import IssueReporter from '../service/IssueReporter';
8
8
 
9
9
  const logger = IssueReporter.loggerFor('state-machine');
10
10
 
11
- export const getState = (actor: SoftphoneActorRef | CallActorRef): SoftphoneStates | CallStates | EstablishedStateTypes => {
12
- return actor.getSnapshot().value as SoftphoneStates | CallStates | EstablishedStateTypes;
11
+ export const getState = (actor: SoftphoneActorRef | CallActorRef): SoftphoneStates | CallStates | EstablishedStateTypes | Record<CallStates, EstablishedStateTypes> => {
12
+ return actor.getSnapshot().value as SoftphoneStates | CallStates | EstablishedStateTypes | Record<CallStates, EstablishedStateTypes>;
13
13
  };
14
14
 
15
15
  export const can = (actor: SoftphoneActorRef | CallActorRef, action: SoftphoneActions | CallActions | EstablishedCallActions): boolean => {
package/src/utils/sdp.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-underscore-dangle */
1
2
  import sdpParser from 'sdp-transform';
2
3
  import { UserAgent } from 'sip.js/lib/api/user-agent';
3
4
  import { Inviter } from 'sip.js/lib/api';
@@ -174,3 +175,18 @@ export const replaceLocalIpModifier = (description: Record<string, any>) => Prom
174
175
  });
175
176
 
176
177
  export const makeURI = (target: string, host: string): URI | undefined => UserAgent.makeURI(`sip:${target}@${host}`);
178
+
179
+ export const getCallNumber = (sipCall: SipCall): string => {
180
+ // eslint-disable-next-line
181
+ const identity = sipCall ? sipCall.remoteIdentity || sipCall.assertedIdentity : null;
182
+ // @ts-ignore: private
183
+ return identity ? identity.uri._normal.user : null;
184
+ };
185
+
186
+ export const getCallDisplayName = (sipCall: SipCall): string => {
187
+ // eslint-disable-next-line
188
+ const identity = sipCall ? sipCall.remoteIdentity || sipCall.assertedIdentity : null;
189
+ const number = getCallNumber(sipCall);
190
+
191
+ return identity ? identity.displayName || number : number;
192
+ };
@@ -1,4 +1,4 @@
1
- /* eslint-disable no-underscore-dangle, no-param-reassign */
1
+ /* eslint-disable no-underscore-dangle, no-param-reassign, no-restricted-syntax */
2
2
  import type { SessionDescriptionHandler } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler';
3
3
  import type { SessionDescriptionHandlerFactoryOptions } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-factory-options';
4
4
  import type { SessionDescriptionHandlerConfiguration } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-configuration';
@@ -246,6 +246,28 @@ export const toggleVideo = (sipCall: SipCall, muteCamera: boolean): void => {
246
246
  }
247
247
  };
248
248
 
249
+ export const isVideoMuted = (sipCall: SipCall): boolean => {
250
+ const sdh = sipCall.sessionDescriptionHandler as SessionDescriptionHandler;
251
+ const pc = sdh?.peerConnection as PeerConnection;
252
+
253
+ if (pc?.getSenders) {
254
+ for (const sender of pc.getSenders()) {
255
+ if (sender && sender.track && sender.track.kind === 'video') {
256
+ return !sender.track.enabled;
257
+ }
258
+ }
259
+ } else {
260
+ for (const stream of pc.getLocalStreams()) {
261
+ // eslint-disable-next-line no-unreachable-loop
262
+ for (const track of stream.getVideoTracks()) {
263
+ return !track.enabled;
264
+ }
265
+ }
266
+ }
267
+
268
+ return true;
269
+ };
270
+
249
271
  export const cleanupStream = (stream: MediaStream): void => {
250
272
  stream.getTracks().filter(track => track.enabled).forEach(track => track.stop());
251
273
  };