@wazo/sdk 1.0.0-alpha.2 → 1.0.0-alpha.20

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 (158) hide show
  1. package/README.md +16 -7
  2. package/dist/wazo-sdk.js +2 -2
  3. package/esm/__tests__/__voice__/call.test.js +52 -3
  4. package/esm/api/auth.js +3 -0
  5. package/esm/checker/checks/webrtc.js +2 -2
  6. package/esm/domain/CallLog.js +2 -3
  7. package/esm/domain/Phone/CTIPhone.js +2 -2
  8. package/esm/domain/Phone/WebRTCPhone.js +59 -57
  9. package/esm/index.js +3 -1
  10. package/esm/service/CallApi.js +4 -4
  11. package/esm/simple/Auth.js +13 -3
  12. package/esm/simple/Phone.js +5 -5
  13. package/esm/simple/index.js +2 -1
  14. package/esm/simple/room/Room.js +2 -2
  15. package/esm/simple/room/SipRoom.js +2 -2
  16. package/esm/state-machine/call-state-machine.js +34 -19
  17. package/esm/state-machine/softphone-state-machine.js +1 -2
  18. package/esm/state-machine/utils.js +10 -4
  19. package/esm/types/api/auth.d.ts +3 -0
  20. package/esm/types/api/auth.d.ts.map +1 -1
  21. package/esm/types/checker/checks/index.d.ts +1 -1
  22. package/esm/types/checker/checks/webrtc-transport.d.ts +2 -2
  23. package/esm/types/checker/checks/webrtc-transport.d.ts.map +1 -1
  24. package/esm/types/domain/CallLog.d.ts.map +1 -1
  25. package/esm/types/domain/CallSession.d.ts +3 -3
  26. package/esm/types/domain/CallSession.d.ts.map +1 -1
  27. package/esm/types/domain/Phone/CTIPhone.d.ts.map +1 -1
  28. package/esm/types/domain/Phone/WebRTCPhone.d.ts +22 -22
  29. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  30. package/esm/types/domain/types.d.ts +0 -4
  31. package/esm/types/domain/types.d.ts.map +1 -1
  32. package/esm/types/index.d.ts +3 -1
  33. package/esm/types/index.d.ts.map +1 -1
  34. package/esm/types/lib/WazoSessionDescriptionHandler.d.ts +3 -3
  35. package/esm/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
  36. package/esm/types/service/CallApi.d.ts +5 -5
  37. package/esm/types/service/CallApi.d.ts.map +1 -1
  38. package/esm/types/simple/Auth.d.ts +6 -1
  39. package/esm/types/simple/Auth.d.ts.map +1 -1
  40. package/esm/types/simple/Phone.d.ts +8 -8
  41. package/esm/types/simple/Phone.d.ts.map +1 -1
  42. package/esm/types/simple/index.d.ts +3 -1
  43. package/esm/types/simple/index.d.ts.map +1 -1
  44. package/esm/types/state-machine/call-state-machine.d.ts +12 -1
  45. package/esm/types/state-machine/call-state-machine.d.ts.map +1 -1
  46. package/esm/types/state-machine/softphone-state-machine.d.ts +1 -2
  47. package/esm/types/state-machine/softphone-state-machine.d.ts.map +1 -1
  48. package/esm/types/state-machine/utils.d.ts +1 -1
  49. package/esm/types/state-machine/utils.d.ts.map +1 -1
  50. package/esm/types/utils/sdp.d.ts +1 -2
  51. package/esm/types/utils/sdp.d.ts.map +1 -1
  52. package/esm/types/utils/update-from.d.ts +1 -1
  53. package/esm/types/utils/update-from.d.ts.map +1 -1
  54. package/esm/types/utils/webrtc.d.ts.map +1 -1
  55. package/esm/types/voice/call.d.ts +15 -3
  56. package/esm/types/voice/call.d.ts.map +1 -1
  57. package/esm/types/voice/softphone.d.ts +7 -0
  58. package/esm/types/voice/softphone.d.ts.map +1 -1
  59. package/esm/types/web-rtc-client.d.ts +39 -38
  60. package/esm/types/web-rtc-client.d.ts.map +1 -1
  61. package/esm/utils/sdp.js +16 -9
  62. package/esm/utils/update-from.js +2 -2
  63. package/esm/utils/webrtc.js +10 -4
  64. package/esm/voice/call.js +86 -28
  65. package/esm/voice/softphone.js +58 -17
  66. package/esm/web-rtc-client.js +67 -58
  67. package/lib/__tests__/__voice__/call.test.js +51 -2
  68. package/lib/api/auth.js +3 -0
  69. package/lib/checker/checks/webrtc.js +1 -1
  70. package/lib/domain/CallLog.js +2 -3
  71. package/lib/domain/Phone/CTIPhone.js +2 -2
  72. package/lib/domain/Phone/WebRTCPhone.js +58 -56
  73. package/lib/index.js +7 -3
  74. package/lib/service/CallApi.js +2 -2
  75. package/lib/simple/Auth.js +15 -3
  76. package/lib/simple/Phone.js +2 -2
  77. package/lib/simple/index.js +2 -1
  78. package/lib/simple/room/Room.js +1 -1
  79. package/lib/simple/room/SipRoom.js +1 -1
  80. package/lib/state-machine/call-state-machine.js +35 -20
  81. package/lib/state-machine/softphone-state-machine.js +1 -2
  82. package/lib/state-machine/utils.js +10 -4
  83. package/lib/types/api/auth.d.ts +3 -0
  84. package/lib/types/api/auth.d.ts.map +1 -1
  85. package/lib/types/checker/checks/index.d.ts +1 -1
  86. package/lib/types/checker/checks/webrtc-transport.d.ts +2 -2
  87. package/lib/types/checker/checks/webrtc-transport.d.ts.map +1 -1
  88. package/lib/types/domain/CallLog.d.ts.map +1 -1
  89. package/lib/types/domain/CallSession.d.ts +3 -3
  90. package/lib/types/domain/CallSession.d.ts.map +1 -1
  91. package/lib/types/domain/Phone/CTIPhone.d.ts.map +1 -1
  92. package/lib/types/domain/Phone/WebRTCPhone.d.ts +22 -22
  93. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  94. package/lib/types/domain/types.d.ts +0 -4
  95. package/lib/types/domain/types.d.ts.map +1 -1
  96. package/lib/types/index.d.ts +3 -1
  97. package/lib/types/index.d.ts.map +1 -1
  98. package/lib/types/lib/WazoSessionDescriptionHandler.d.ts +3 -3
  99. package/lib/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
  100. package/lib/types/service/CallApi.d.ts +5 -5
  101. package/lib/types/service/CallApi.d.ts.map +1 -1
  102. package/lib/types/simple/Auth.d.ts +6 -1
  103. package/lib/types/simple/Auth.d.ts.map +1 -1
  104. package/lib/types/simple/Phone.d.ts +8 -8
  105. package/lib/types/simple/Phone.d.ts.map +1 -1
  106. package/lib/types/simple/index.d.ts +3 -1
  107. package/lib/types/simple/index.d.ts.map +1 -1
  108. package/lib/types/state-machine/call-state-machine.d.ts +12 -1
  109. package/lib/types/state-machine/call-state-machine.d.ts.map +1 -1
  110. package/lib/types/state-machine/softphone-state-machine.d.ts +1 -2
  111. package/lib/types/state-machine/softphone-state-machine.d.ts.map +1 -1
  112. package/lib/types/state-machine/utils.d.ts +1 -1
  113. package/lib/types/state-machine/utils.d.ts.map +1 -1
  114. package/lib/types/utils/sdp.d.ts +1 -2
  115. package/lib/types/utils/sdp.d.ts.map +1 -1
  116. package/lib/types/utils/update-from.d.ts +1 -1
  117. package/lib/types/utils/update-from.d.ts.map +1 -1
  118. package/lib/types/utils/webrtc.d.ts.map +1 -1
  119. package/lib/types/voice/call.d.ts +15 -3
  120. package/lib/types/voice/call.d.ts.map +1 -1
  121. package/lib/types/voice/softphone.d.ts +7 -0
  122. package/lib/types/voice/softphone.d.ts.map +1 -1
  123. package/lib/types/web-rtc-client.d.ts +39 -38
  124. package/lib/types/web-rtc-client.d.ts.map +1 -1
  125. package/lib/utils/sdp.js +17 -10
  126. package/lib/utils/update-from.js +2 -2
  127. package/lib/utils/webrtc.js +11 -4
  128. package/lib/voice/call.js +89 -27
  129. package/lib/voice/softphone.js +62 -17
  130. package/lib/web-rtc-client.js +66 -57
  131. package/package.json +1 -1
  132. package/src/__tests__/__voice__/call.test.ts +61 -3
  133. package/src/api/auth.ts +5 -0
  134. package/src/checker/checks/webrtc-transport.ts +2 -2
  135. package/src/checker/checks/webrtc.ts +2 -2
  136. package/src/domain/CallLog.ts +2 -3
  137. package/src/domain/CallSession.ts +3 -3
  138. package/src/domain/Phone/CTIPhone.ts +3 -2
  139. package/src/domain/Phone/WebRTCPhone.ts +87 -85
  140. package/src/domain/__tests__/CallSession.test.ts +4 -0
  141. package/src/domain/types.ts +0 -5
  142. package/src/index.ts +4 -1
  143. package/src/lib/WazoSessionDescriptionHandler.ts +3 -3
  144. package/src/service/CallApi.ts +7 -7
  145. package/src/simple/Auth.ts +15 -3
  146. package/src/simple/Phone.ts +11 -11
  147. package/src/simple/index.ts +2 -1
  148. package/src/simple/room/Room.ts +3 -3
  149. package/src/simple/room/SipRoom.ts +3 -3
  150. package/src/state-machine/call-state-machine.ts +38 -19
  151. package/src/state-machine/softphone-state-machine.ts +1 -2
  152. package/src/state-machine/utils.ts +12 -4
  153. package/src/utils/sdp.ts +18 -11
  154. package/src/utils/update-from.ts +2 -2
  155. package/src/utils/webrtc.ts +11 -4
  156. package/src/voice/call.ts +109 -36
  157. package/src/voice/softphone.ts +75 -18
  158. package/src/web-rtc-client.ts +112 -98
package/src/voice/call.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable no-underscore-dangle */
2
2
  import { EventEmitter } from 'events';
3
3
  import { createActor } from 'xstate';
4
+ import { Inviter } from 'sip.js/lib/api/inviter';
4
5
  import { Invitation } from 'sip.js/lib/api/invitation';
5
6
  import { SessionState } from 'sip.js/lib/api/session-state';
6
7
  import { OutgoingInviteRequest } from 'sip.js/lib/core';
@@ -8,15 +9,16 @@ import { SessionDescriptionHandler } from 'sip.js/lib/platform/web';
8
9
  import { SessionDescriptionHandlerOptions } from 'sip.js/lib/api';
9
10
  import type { IncomingRequestMessage } from 'sip.js/lib/core/messages/incoming-request-message';
10
11
 
11
- import callStateMachine, { type ActionTypes, Actions, type EstablishedActionTypes, EstablishedActions, States, type StateTypes, EstablishedStates, type EstablishedStateTypes, type CallActorRef } from '../state-machine/call-state-machine';
12
+ import callStateMachine, { type ActionTypes, Actions, type EstablishedActionTypes, EstablishedActions, States, type StateTypes, EstablishedStates, type EstablishedStateTypes, type CallActorRef, EstablishedSubStateTypes } from '../state-machine/call-state-machine';
12
13
  import { SipCall, PeerConnection } from '../domain/types';
13
14
  import ApiCall from '../domain/Call';
14
-
15
+ import newFrom from '../utils/new-from';
16
+ import updateFrom from '../utils/update-from';
15
17
  import IssueReporter from '../service/IssueReporter';
16
- import softphone, { EVENT_TERMINATE_SOUND, Softphone } from './softphone';
17
- import { getCallDisplayName, getCallNumber, getSipSessionId } from '../utils/sdp';
18
+ import softphone, { Softphone } from './softphone';
19
+ import { getCallDisplayName, getCallNumber, getSipCallId } from '../utils/sdp';
18
20
  import { assertCan, assertState, getState } from '../state-machine/utils';
19
- import { downgradeToAudio, getLocalStream, getLocalVideoStream, getPeerConnection, getRemoteStream, getRemoteVideoStream, getStreamFromConstraints, getWebRtcStats, hasALocalVideoTrack, hasLocalVideo, hasRemoteVideo, isVideoMuted, isVideoRemotelyHeld, setLocalMediaStream, toggleVideo, upgradeToVideo } from '../utils/webrtc';
21
+ import { downgradeToAudio, getLocalStream, getLocalVideoStream, getPeerConnection, getRemoteStream, getRemoteVideoStream, getStreamFromConstraints, getWebRtcStats, hasALocalVideoTrack, hasLocalVideo, hasRemoteVideo, isVideoMuted, isVideoRemotelyHeld, sessionWantsToDoVideo, setLocalMediaStream, toggleVideo, upgradeToVideo } from '../utils/webrtc';
20
22
 
21
23
  const logger = IssueReporter.loggerFor('call');
22
24
 
@@ -32,7 +34,8 @@ export type ReinviteOptions = {
32
34
  };
33
35
 
34
36
  // Events
35
- export const EVENT_CALL_ACCEPTED = 'accepted';
37
+ export const EVENT_CALL_REMOTLY_ACCEPTED = 'remotelyAccepted';
38
+ export const EVENT_CALL_LOCALLY_ACCEPTED = 'locallyAccepted';
36
39
  export const EVENT_CALL_CANCELED = 'canceled';
37
40
  export const EVENT_PROGRESS = 'progress';
38
41
  export const EVENT_EARLY_MEDIA = 'earlyMedia';
@@ -60,6 +63,8 @@ export const MESSAGE_TYPE_CHAT = 'message/TYPE_CHAT';
60
63
  export const MESSAGE_TYPE_SIGNAL = 'message/TYPE_SIGNAL';
61
64
  export const MESSAGE_TRACK_UPDATED = 'onTrackUpdated';
62
65
 
66
+ const attributesToUpdate = ['apiId', 'talkingToIds', 'creationTime', 'answerTime', 'endTime', 'recording', 'recordingPaused', 'displayName', 'number'];
67
+
63
68
  class Call extends EventEmitter {
64
69
  callActor: CallActorRef;
65
70
 
@@ -72,7 +77,7 @@ class Call extends EventEmitter {
72
77
 
73
78
  creationTime: Date;
74
79
 
75
- answerTime: Date;
80
+ answerTime: Date | null;
76
81
 
77
82
  endTime: Date;
78
83
 
@@ -82,12 +87,18 @@ class Call extends EventEmitter {
82
87
 
83
88
  recording = false;
84
89
 
90
+ recordingPaused = false;
91
+
85
92
  displayName: string | null;
86
93
 
87
94
  number: string | null;
88
95
 
89
96
  currentScreenShare: Record<string, any> | null;
90
97
 
98
+ static newFrom(call: Call) {
99
+ return newFrom(call, Call);
100
+ }
101
+
91
102
  static parseCall(apiCall: ApiCall): Call {
92
103
  const sipCall = {
93
104
  id: apiCall.sipCallId,
@@ -97,32 +108,44 @@ class Call extends EventEmitter {
97
108
  remoteIdentity: {
98
109
  uri: {
99
110
  _normal: {
100
- user: apiCall.calleeNumber,
111
+ user: apiCall.calleeName || apiCall.calleeNumber,
101
112
  },
102
113
  },
103
114
  },
104
115
  // Mock sdk so `hasLocalVideo` will return true.
105
116
  sessionDescriptionHandler: {
106
117
  localMediaStream: {
107
- getTracks: () => (apiCall.isVideo ? [{ kind: 'video', readyState: 'live' }] : []),
118
+ getTracks: () => (apiCall.isVideo ? [{ id: 'mocked', kind: 'video', readyState: 'live' }] : []),
108
119
  },
109
120
  },
110
121
  } as any;
111
122
 
112
123
  const call = new Call(sipCall, softphone);
113
- // call._sendAction(Actions.ES);
114
124
  call.apiId = apiCall.id;
115
- call.answerTime = new Date(+apiCall.startingTime);
125
+ call.creationTime = new Date(+apiCall.startingTime);
126
+ call.recording = apiCall.isRecording();
116
127
 
117
128
  // Update call state
118
129
  const snapshot = call.callActor.getSnapshot();
119
130
 
120
131
  if (apiCall.isUp()) {
121
- snapshot.value = { [States.ESTABLISHED]: EstablishedStates.ONGOING };
132
+ snapshot.value = {
133
+ [States.ESTABLISHED]: {
134
+ [EstablishedStates.ONGOING]: {},
135
+ muteState: EstablishedStates.UN_MUTED,
136
+ holdState: EstablishedStates.UN_HELD,
137
+ },
138
+ };
122
139
  }
123
140
 
124
141
  if (apiCall.muted) {
125
- snapshot.value = { [States.ESTABLISHED]: EstablishedStates.MUTED };
142
+ snapshot.value = {
143
+ [States.ESTABLISHED]: {
144
+ [EstablishedStates.ONGOING]: {},
145
+ muteState: EstablishedStates.MUTED,
146
+ holdState: EstablishedStates.UN_HELD,
147
+ },
148
+ };
126
149
  }
127
150
 
128
151
  if (apiCall.isRinging()) {
@@ -152,8 +175,6 @@ class Call extends EventEmitter {
152
175
 
153
176
  this.number = getCallNumber(sipCall);
154
177
  this.displayName = getCallDisplayName(sipCall);
155
-
156
- this._bindEvents();
157
178
  }
158
179
 
159
180
  async accept(options: AcceptOptions = {}): Promise<string | null> {
@@ -170,6 +191,8 @@ class Call extends EventEmitter {
170
191
  return this.phone.client.answer(this.sipCall as Invitation, options.withCamera).then(() => {
171
192
  logger.info('call - accepted', { id: this.id, ...options });
172
193
 
194
+ this.emit(EVENT_CALL_LOCALLY_ACCEPTED, this);
195
+
173
196
  return this.id;
174
197
  }).catch(e => {
175
198
  logger.error(e);
@@ -182,19 +205,21 @@ class Call extends EventEmitter {
182
205
  assertCan(this.callActor, Actions.REJECT);
183
206
  logger.info('call - reject', { id: this.id });
184
207
 
185
- this.phone.emit(EVENT_TERMINATE_SOUND, 'call rejected locally');
186
-
187
- this.shouldSendReinvite = false;
188
-
189
- return this.hangup();
208
+ return this._terminate();
190
209
  }
191
210
 
192
211
  async hangup(): Promise<boolean> {
193
212
  assertCan(this.callActor, Actions.HANGUP);
194
213
  logger.info('call - hangup', { id: this.id });
195
214
 
215
+ return this._terminate();
216
+ }
217
+
218
+ async _terminate(): Promise<boolean> {
196
219
  await this.phone.client.hangup(this.sipCall);
197
220
 
221
+ this.shouldSendReinvite = false;
222
+
198
223
  // Remove this call from softphone's calls
199
224
  this.phone.onCallTerminated(this);
200
225
 
@@ -318,7 +343,7 @@ class Call extends EventEmitter {
318
343
  turnCameraOn(): void {
319
344
  assertState(this.callActor, States.ESTABLISHED);
320
345
  logger.info('call - turn camera on', { id: this.id });
321
- toggleVideo(this.sipCall, true);
346
+ toggleVideo(this.sipCall, false);
322
347
 
323
348
  this.emit(EVENT_CAMERA_RESUMED);
324
349
  }
@@ -326,13 +351,13 @@ class Call extends EventEmitter {
326
351
  turnCameraOff(): void {
327
352
  assertState(this.callActor, States.ESTABLISHED);
328
353
  logger.info('call - turn camera off', { id: this.id });
329
- toggleVideo(this.sipCall, false);
354
+ toggleVideo(this.sipCall, true);
330
355
 
331
356
  this.emit(EVENT_CAMERA_DISABLED);
332
357
  }
333
358
 
334
359
  isMuted() {
335
- return this._hasEstablishedState(EstablishedStates.MUTED);
360
+ return this._hasEstablishedState('muteState', EstablishedStates.MUTED);
336
361
  }
337
362
 
338
363
  isCameraMuted() {
@@ -340,11 +365,27 @@ class Call extends EventEmitter {
340
365
  }
341
366
 
342
367
  isHeld() {
343
- return this._hasEstablishedState(EstablishedStates.HELD);
368
+ return this._hasEstablishedState('holdState', EstablishedStates.HELD);
369
+ }
370
+
371
+ isEstablishing() {
372
+ return this.sipCall.state === SessionState.Establishing;
344
373
  }
345
374
 
346
375
  isEstablished() {
347
- return States.ESTABLISHED in (this.state as Record<StateTypes, EstablishedStateTypes>);
376
+ return typeof this.state === 'object' && States.ESTABLISHED in (this.state as Record<StateTypes, EstablishedStateTypes>);
377
+ }
378
+
379
+ isRinging() {
380
+ return this.state === States.RINGING;
381
+ }
382
+
383
+ isOutgoing() {
384
+ return this.sipCall instanceof Inviter;
385
+ }
386
+
387
+ isIncoming() {
388
+ return !this.isOutgoing();
348
389
  }
349
390
 
350
391
  isConference(): boolean {
@@ -355,7 +396,7 @@ class Call extends EventEmitter {
355
396
  assertState(this.callActor, States.ESTABLISHED);
356
397
  logger.info('call - transfer', { id: this.id, target });
357
398
 
358
- this.phone.client.transfer(this.sipCall, target);
399
+ return this.phone.client.transfer(this.sipCall, target);
359
400
  }
360
401
 
361
402
  atxfer(): Record<string, any> | null | undefined {
@@ -526,9 +567,17 @@ class Call extends EventEmitter {
526
567
  this._onScreenSharing(localStream as MediaStream, false, desktop);
527
568
  }
528
569
 
570
+ onMakeCall() {
571
+ this._bindEvents();
572
+
573
+ this._sendAction(Actions.MAKE_CALL);
574
+ }
575
+
529
576
  onAccepted() {
577
+ this.answerTime = new Date();
578
+
530
579
  this._sendAction(Actions.REMOTLY_ACCEPTED);
531
- this.emit(EVENT_CALL_ACCEPTED);
580
+ this.emit(EVENT_CALL_REMOTLY_ACCEPTED);
532
581
  }
533
582
 
534
583
  onProgress() {
@@ -549,6 +598,8 @@ class Call extends EventEmitter {
549
598
 
550
599
  onCallIncoming() {
551
600
  this._sendAction(Actions.INCOMING_CALL);
601
+
602
+ this._bindEvents();
552
603
  }
553
604
 
554
605
  onRejected() {
@@ -579,6 +630,10 @@ class Call extends EventEmitter {
579
630
  return hasLocalVideo(this.sipCall);
580
631
  }
581
632
 
633
+ wantsToDoVideo() {
634
+ return sessionWantsToDoVideo(this.sipCall);
635
+ }
636
+
582
637
  getLocalStream() {
583
638
  return getLocalStream(this.sipCall);
584
639
  }
@@ -595,25 +650,41 @@ class Call extends EventEmitter {
595
650
  return isVideoRemotelyHeld(this.sipCall);
596
651
  }
597
652
 
598
- isRinging() {
599
- return this.state === States.RINGING;
600
- }
601
-
602
653
  onReinvite(request: IncomingRequestMessage, updatedCalleeName: string, updatedNumber: string, hadRemoteVideo: boolean) {
603
654
  this.emit(EVENT_REINVITE, request, updatedCalleeName, updatedNumber, hadRemoteVideo);
604
655
  }
605
656
 
657
+ updateFrom(call: Call) {
658
+ updateFrom(this, call, attributesToUpdate);
659
+ }
660
+
661
+ is(otherCall: Call) {
662
+ return otherCall && this.id && this.id === otherCall.id;
663
+ }
664
+
665
+ shouldAutoAnswer() {
666
+ return !!this.sipCall.request?.getHeader('alert-info');
667
+ }
668
+
606
669
  get id() {
607
- return getSipSessionId(this.sipCall);
670
+ return getSipCallId(this.sipCall);
608
671
  }
609
672
 
610
673
  get state(): StateTypes | Record<StateTypes, EstablishedStateTypes> {
611
674
  return getState(this.callActor) as StateTypes | Record<StateTypes, EstablishedStateTypes>;
612
675
  }
613
676
 
614
- private _hasEstablishedState(establishedState: EstablishedStateTypes) {
615
- const state = this.state as Record<StateTypes, EstablishedStateTypes>;
616
- return States.ESTABLISHED in state && state[States.ESTABLISHED] === establishedState;
677
+ private _hasEstablishedState(subState: EstablishedSubStateTypes, establishedState: EstablishedStateTypes) {
678
+ const state = this.state as Record<StateTypes, EstablishedStateTypes | Record<EstablishedSubStateTypes, EstablishedStateTypes>>;
679
+ if (typeof state !== 'object') {
680
+ return false;
681
+ }
682
+
683
+ if (!(States.ESTABLISHED in state)) {
684
+ return false;
685
+ }
686
+
687
+ return (state[States.ESTABLISHED] as Record<EstablishedSubStateTypes, EstablishedStateTypes>)[subState] === establishedState;
617
688
  }
618
689
 
619
690
  private _bindEvents() {
@@ -630,7 +701,7 @@ class Call extends EventEmitter {
630
701
  };
631
702
  }
632
703
 
633
- this.sipCall.stateChange.addListener((newState: SessionState) => {
704
+ this.sipCall?.stateChange?.addListener((newState: SessionState) => {
634
705
  switch (newState) {
635
706
  case SessionState.Terminating:
636
707
  logger.info('call - terminating', { id: this.id });
@@ -784,7 +855,9 @@ class Call extends EventEmitter {
784
855
  }
785
856
 
786
857
  private _sendAction(action: ActionTypes | EstablishedActionTypes) {
858
+ const was = this.state;
787
859
  this.callActor.send({ type: action });
860
+ logger.trace('call - send action', { id: this.id, action, was, state: this.state });
788
861
  }
789
862
  }
790
863
 
@@ -16,6 +16,7 @@ import IssueReporter from '../service/IssueReporter';
16
16
  import configureLogger from '../utils/sip-logger';
17
17
  import Call from './call';
18
18
  import { assertCan, can, getState, waitUntilState } from '../state-machine/utils';
19
+ import { getSipCallId } from '../utils/sdp';
19
20
 
20
21
  export const EVENT_INCOMING = 'incoming';
21
22
  export const EVENT_OUTGOING = 'outgoing';
@@ -44,6 +45,7 @@ export type CallOptions = {
44
45
  // If audioOnly is set to true, all video stream will be deactivated, even remotes ones.
45
46
  audioOnly?: boolean,
46
47
  conference?: boolean,
48
+ extraHeaders?: Record<string, any>,
47
49
  };
48
50
 
49
51
  const logger = IssueReporter.loggerFor('softphone');
@@ -91,6 +93,8 @@ export class Softphone extends EventEmitter {
91
93
  const server = Wazo.Auth.getHost();
92
94
  const session = Wazo.Auth.getSession();
93
95
 
96
+ logger.info('softphone - connect', { server, session: !!session });
97
+
94
98
  if (!server || !session) {
95
99
  throw new Error('Please connect to the server using `Wazo.Auth.logIn` or `Wazo.Auth.authenticate` before using Room.connect().');
96
100
  }
@@ -107,16 +111,25 @@ export class Softphone extends EventEmitter {
107
111
  return this.connectWithCredentials(server, this.sipLine, session.displayName(), options);
108
112
  }
109
113
 
114
+ async hangupAllCalls() {
115
+ return Promise.all(this.calls.map(call => call.hangup()));
116
+ }
117
+
110
118
  async disconnect() {
111
119
  assertCan(this.softphoneActor, Actions.UNREGISTER);
120
+ logger.info('softphone - disconnect');
121
+
112
122
  if (!this.client) {
123
+ logger.info('softphone - disconnect, not client to disconnect, bailing');
113
124
  return null;
114
125
  }
115
126
 
116
127
  // Terminate all calls
117
- await Promise.all(this.calls.map(call => call.hangup()));
128
+ await this.hangupAllCalls();
118
129
 
119
- return this.client.unregister();
130
+ await this.client.unregister();
131
+
132
+ return this.client.close();
120
133
  }
121
134
 
122
135
  getPrimaryWebRtcLine(): SipLine | null {
@@ -131,7 +144,9 @@ export class Softphone extends EventEmitter {
131
144
  ): Promise<void> {
132
145
  assertCan(this.softphoneActor, Actions.REGISTER);
133
146
 
134
- this.softphoneActor.send({ type: Actions.REGISTER });
147
+ logger.info('softphone - connect with credentials', { server, username: sipLine.username, displayName, options: rawOptions });
148
+
149
+ this._sendAction(Actions.REGISTER);
135
150
 
136
151
  const [host, port = 443] = server.split(':');
137
152
  let options = { ...rawOptions };
@@ -171,7 +186,7 @@ export class Softphone extends EventEmitter {
171
186
  return;
172
187
  }
173
188
 
174
- this.client.attemptReconnection();
189
+ this.client.register();
175
190
  }
176
191
 
177
192
  async call(options: CallOptions): Promise<Call | null> {
@@ -189,7 +204,7 @@ export class Softphone extends EventEmitter {
189
204
  await waitUntilState(this.softphoneActor, States.REGISTERED);
190
205
  }
191
206
 
192
- if (this.state !== States.UNREGISTERED) {
207
+ if (this.state !== States.REGISTERED) {
193
208
  throw new InvalidState(`Invalid state ${States.UNREGISTERED}`, States.UNREGISTERED);
194
209
  }
195
210
 
@@ -201,7 +216,9 @@ export class Softphone extends EventEmitter {
201
216
  try {
202
217
  sipCall = this.client.call(options.params.To, options.withCamera, options.audioOnly, options.conference, options) as SipCall;
203
218
  const call = new Call(sipCall, this);
219
+ call.onMakeCall();
204
220
 
221
+ this.calls.push(call);
205
222
  this.emit(EVENT_OUTGOING, call);
206
223
 
207
224
  return call;
@@ -279,11 +296,13 @@ export class Softphone extends EventEmitter {
279
296
  }
280
297
 
281
298
  holdAllCalls(exceptCall: Call | null = null) {
282
- this.calls.filter(call => !call.isHeld() && call.id === exceptCall?.id).forEach(call => call.hold());
299
+ logger.info('softphone - hold all calls', { exceptId: exceptCall?.id, ids: this.calls.map(call => call.id) });
300
+
301
+ this.calls.filter(call => !call.isHeld() && call.id !== exceptCall?.id).forEach(call => call.hold());
283
302
  }
284
303
 
285
304
  startHeartbeat() {
286
- logger.info('softphone - start heartbeat', { client: !!this.client, hasHeartbeat: this.client.hasHeartbeat() });
305
+ logger.info('softphone - start heartbeat', { client: !!this.client, hasHeartbeat: this.client?.hasHeartbeat() });
287
306
  if (!this.client || this.client.hasHeartbeat()) {
288
307
  return;
289
308
  }
@@ -348,7 +367,11 @@ export class Softphone extends EventEmitter {
348
367
  }
349
368
 
350
369
  getCurrentCall(): Call | undefined {
351
- return this.calls.find(call => call.isEstablished());
370
+ return this.getActiveCalls()[0];
371
+ }
372
+
373
+ getActiveCalls(): Call[] {
374
+ return this.calls.filter(call => call.isEstablished());
352
375
  }
353
376
 
354
377
  getIncomingCalls(): Call[] {
@@ -359,14 +382,26 @@ export class Softphone extends EventEmitter {
359
382
  return getState(this.softphoneActor) as StateTypes;
360
383
  }
361
384
 
385
+ get user() {
386
+ return this.client?.userAgent?.contact?.uri?.user;
387
+ }
388
+
362
389
  getUserAgent() {
363
390
  return this.client?.config?.userAgentString || 'softphone';
364
391
  }
365
392
 
393
+ isRegistering() {
394
+ return this.state === States.REGISTERING;
395
+ }
396
+
366
397
  isRegistered() {
367
398
  return this.state === States.REGISTERED;
368
399
  }
369
400
 
401
+ isUnRegistered() {
402
+ return this.state === States.UNREGISTERED;
403
+ }
404
+
370
405
  _bindEvents(): void {
371
406
  this.client.on(this.client.INVITE, (sipSession: SipCall) => {
372
407
  const call = new Call(sipSession, this);
@@ -388,7 +423,7 @@ export class Softphone extends EventEmitter {
388
423
 
389
424
  this.client.on(this.client.ON_REINVITE, (sipCall: SipCall, request: IncomingRequestMessage, updatedCalleeName: string, updatedNumber: string, hadRemoteVideo: boolean) => {
390
425
  logger.info('softphone - on reinvite', {
391
- callId: sipCall.id,
426
+ callId: getSipCallId(sipCall),
392
427
  inviteId: request.callId,
393
428
  updatedCalleeName,
394
429
  updatedNumber,
@@ -440,22 +475,32 @@ export class Softphone extends EventEmitter {
440
475
 
441
476
  this.client.on(this.client.UNREGISTERED, () => {
442
477
  logger.info('softphone - unregistered');
443
- this.emit(EVENT_UNREGISTERED);
478
+ if (!can(this.softphoneActor, Actions.UNREGISTER)) {
479
+ logger.warn('softphone - cannot unregister', { state: this.state });
480
+ return;
481
+ }
482
+
483
+ this._sendAction(Actions.UNREGISTER);
444
484
 
445
- this._sendAction(Actions.UNREGISTERED);
485
+ this.emit(EVENT_UNREGISTERED);
446
486
  });
447
487
 
448
488
  this.client.on(this.client.ON_DISCONNECTED, () => {
449
489
  logger.info('softphone - disconnected');
450
- this.emit(EVENT_DISCONNECTED);
490
+ if (!can(this.softphoneActor, Actions.TRANSPORT_CLOSED)) {
491
+ logger.warn('softphone - cannot disconnected', { state: this.state });
492
+ return;
493
+ }
451
494
 
452
495
  this._sendAction(Actions.TRANSPORT_CLOSED);
496
+
497
+ this.emit(EVENT_DISCONNECTED);
453
498
  });
454
499
 
455
500
  this.client.on(this.client.REGISTERED, () => {
456
- logger.info('softphone - registered', { state: getState(this.softphoneActor) });
501
+ logger.info('softphone - registered', { state: getState(this.softphoneActor), reinvite: this.shouldSendReinvite });
457
502
  if (!can(this.softphoneActor, Actions.REGISTER_DONE)) {
458
- logger.warn('softphone - registered but not in REGISTERING state', { state: getState(this.softphoneActor) });
503
+ logger.warn('softphone - registered but not in REGISTERING state', { state: this.state });
459
504
  return;
460
505
  }
461
506
 
@@ -476,10 +521,16 @@ export class Softphone extends EventEmitter {
476
521
  });
477
522
 
478
523
  this.client.on(this.client.CONNECTED, () => {
479
- logger.info('softphone - connected');
524
+ logger.info('softphone - connected', { state: this.state });
480
525
  this.stopHeartbeat();
481
526
  });
482
527
 
528
+ this.client.on(this.client.REGISTERING, () => {
529
+ logger.info('softphone - registering', { state: this.state });
530
+
531
+ this._sendAction(Actions.REGISTER);
532
+ });
533
+
483
534
  this.client.on(this.client.DISCONNECTED, () => {
484
535
  logger.info('softphone - disconnected');
485
536
  this.emit(EVENT_DISCONNECTED);
@@ -513,19 +564,25 @@ export class Softphone extends EventEmitter {
513
564
  }
514
565
 
515
566
  private _sendAction(action: ActionTypes) {
567
+ const was = this.state;
516
568
  this.softphoneActor.send({ type: action });
569
+ logger.trace('softphone - send action', { action, was, state: this.state });
517
570
  }
518
571
 
519
572
  private _getCall(sipCall: SipCall): Call | undefined {
520
- return this.calls.find(call => call.id === sipCall.id);
573
+ return this.calls.find(call => call.id === getSipCallId(sipCall));
574
+ }
575
+
576
+ private _getCallIds(): string[] {
577
+ return this.calls.map(call => call.id);
521
578
  }
522
579
 
523
580
  private _onCallEvent(sipCall: SipCall, eventName: string, ...args: any[]) {
524
- logger.warn('softphone - received call event', { eventName, sipId: sipCall.id });
581
+ logger.info('softphone - received call event', { eventName, sipId: getSipCallId(sipCall) });
525
582
 
526
583
  const call = this._getCall(sipCall);
527
584
  if (!call) {
528
- logger.warn('softphone - no call found to send the event', { eventName, sipId: sipCall.id });
585
+ logger.warn('softphone - no call found to send the event', { eventName, sipId: getSipCallId(sipCall), ids: this._getCallIds() });
529
586
  return;
530
587
  }
531
588