@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.
- package/README.md +16 -7
- package/dist/wazo-sdk.js +2 -2
- package/esm/__tests__/__voice__/call.test.js +52 -3
- package/esm/api/auth.js +3 -0
- package/esm/checker/checks/webrtc.js +2 -2
- package/esm/domain/CallLog.js +2 -3
- package/esm/domain/Phone/CTIPhone.js +2 -2
- package/esm/domain/Phone/WebRTCPhone.js +59 -57
- package/esm/index.js +3 -1
- package/esm/service/CallApi.js +4 -4
- package/esm/simple/Auth.js +13 -3
- package/esm/simple/Phone.js +5 -5
- package/esm/simple/index.js +2 -1
- package/esm/simple/room/Room.js +2 -2
- package/esm/simple/room/SipRoom.js +2 -2
- package/esm/state-machine/call-state-machine.js +34 -19
- package/esm/state-machine/softphone-state-machine.js +1 -2
- package/esm/state-machine/utils.js +10 -4
- package/esm/types/api/auth.d.ts +3 -0
- package/esm/types/api/auth.d.ts.map +1 -1
- package/esm/types/checker/checks/index.d.ts +1 -1
- package/esm/types/checker/checks/webrtc-transport.d.ts +2 -2
- package/esm/types/checker/checks/webrtc-transport.d.ts.map +1 -1
- package/esm/types/domain/CallLog.d.ts.map +1 -1
- package/esm/types/domain/CallSession.d.ts +3 -3
- package/esm/types/domain/CallSession.d.ts.map +1 -1
- package/esm/types/domain/Phone/CTIPhone.d.ts.map +1 -1
- package/esm/types/domain/Phone/WebRTCPhone.d.ts +22 -22
- package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/esm/types/domain/types.d.ts +0 -4
- package/esm/types/domain/types.d.ts.map +1 -1
- package/esm/types/index.d.ts +3 -1
- package/esm/types/index.d.ts.map +1 -1
- package/esm/types/lib/WazoSessionDescriptionHandler.d.ts +3 -3
- package/esm/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
- package/esm/types/service/CallApi.d.ts +5 -5
- package/esm/types/service/CallApi.d.ts.map +1 -1
- package/esm/types/simple/Auth.d.ts +6 -1
- package/esm/types/simple/Auth.d.ts.map +1 -1
- package/esm/types/simple/Phone.d.ts +8 -8
- package/esm/types/simple/Phone.d.ts.map +1 -1
- package/esm/types/simple/index.d.ts +3 -1
- package/esm/types/simple/index.d.ts.map +1 -1
- package/esm/types/state-machine/call-state-machine.d.ts +12 -1
- package/esm/types/state-machine/call-state-machine.d.ts.map +1 -1
- package/esm/types/state-machine/softphone-state-machine.d.ts +1 -2
- package/esm/types/state-machine/softphone-state-machine.d.ts.map +1 -1
- package/esm/types/state-machine/utils.d.ts +1 -1
- package/esm/types/state-machine/utils.d.ts.map +1 -1
- package/esm/types/utils/sdp.d.ts +1 -2
- package/esm/types/utils/sdp.d.ts.map +1 -1
- package/esm/types/utils/update-from.d.ts +1 -1
- package/esm/types/utils/update-from.d.ts.map +1 -1
- package/esm/types/utils/webrtc.d.ts.map +1 -1
- package/esm/types/voice/call.d.ts +15 -3
- package/esm/types/voice/call.d.ts.map +1 -1
- package/esm/types/voice/softphone.d.ts +7 -0
- package/esm/types/voice/softphone.d.ts.map +1 -1
- package/esm/types/web-rtc-client.d.ts +39 -38
- package/esm/types/web-rtc-client.d.ts.map +1 -1
- package/esm/utils/sdp.js +16 -9
- package/esm/utils/update-from.js +2 -2
- package/esm/utils/webrtc.js +10 -4
- package/esm/voice/call.js +86 -28
- package/esm/voice/softphone.js +58 -17
- package/esm/web-rtc-client.js +67 -58
- package/lib/__tests__/__voice__/call.test.js +51 -2
- package/lib/api/auth.js +3 -0
- package/lib/checker/checks/webrtc.js +1 -1
- package/lib/domain/CallLog.js +2 -3
- package/lib/domain/Phone/CTIPhone.js +2 -2
- package/lib/domain/Phone/WebRTCPhone.js +58 -56
- package/lib/index.js +7 -3
- package/lib/service/CallApi.js +2 -2
- package/lib/simple/Auth.js +15 -3
- package/lib/simple/Phone.js +2 -2
- package/lib/simple/index.js +2 -1
- package/lib/simple/room/Room.js +1 -1
- package/lib/simple/room/SipRoom.js +1 -1
- package/lib/state-machine/call-state-machine.js +35 -20
- package/lib/state-machine/softphone-state-machine.js +1 -2
- package/lib/state-machine/utils.js +10 -4
- package/lib/types/api/auth.d.ts +3 -0
- package/lib/types/api/auth.d.ts.map +1 -1
- package/lib/types/checker/checks/index.d.ts +1 -1
- package/lib/types/checker/checks/webrtc-transport.d.ts +2 -2
- package/lib/types/checker/checks/webrtc-transport.d.ts.map +1 -1
- package/lib/types/domain/CallLog.d.ts.map +1 -1
- package/lib/types/domain/CallSession.d.ts +3 -3
- package/lib/types/domain/CallSession.d.ts.map +1 -1
- package/lib/types/domain/Phone/CTIPhone.d.ts.map +1 -1
- package/lib/types/domain/Phone/WebRTCPhone.d.ts +22 -22
- package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/lib/types/domain/types.d.ts +0 -4
- package/lib/types/domain/types.d.ts.map +1 -1
- package/lib/types/index.d.ts +3 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/lib/WazoSessionDescriptionHandler.d.ts +3 -3
- package/lib/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
- package/lib/types/service/CallApi.d.ts +5 -5
- package/lib/types/service/CallApi.d.ts.map +1 -1
- package/lib/types/simple/Auth.d.ts +6 -1
- package/lib/types/simple/Auth.d.ts.map +1 -1
- package/lib/types/simple/Phone.d.ts +8 -8
- package/lib/types/simple/Phone.d.ts.map +1 -1
- package/lib/types/simple/index.d.ts +3 -1
- package/lib/types/simple/index.d.ts.map +1 -1
- package/lib/types/state-machine/call-state-machine.d.ts +12 -1
- package/lib/types/state-machine/call-state-machine.d.ts.map +1 -1
- package/lib/types/state-machine/softphone-state-machine.d.ts +1 -2
- package/lib/types/state-machine/softphone-state-machine.d.ts.map +1 -1
- package/lib/types/state-machine/utils.d.ts +1 -1
- package/lib/types/state-machine/utils.d.ts.map +1 -1
- package/lib/types/utils/sdp.d.ts +1 -2
- package/lib/types/utils/sdp.d.ts.map +1 -1
- package/lib/types/utils/update-from.d.ts +1 -1
- package/lib/types/utils/update-from.d.ts.map +1 -1
- package/lib/types/utils/webrtc.d.ts.map +1 -1
- package/lib/types/voice/call.d.ts +15 -3
- package/lib/types/voice/call.d.ts.map +1 -1
- package/lib/types/voice/softphone.d.ts +7 -0
- package/lib/types/voice/softphone.d.ts.map +1 -1
- package/lib/types/web-rtc-client.d.ts +39 -38
- package/lib/types/web-rtc-client.d.ts.map +1 -1
- package/lib/utils/sdp.js +17 -10
- package/lib/utils/update-from.js +2 -2
- package/lib/utils/webrtc.js +11 -4
- package/lib/voice/call.js +89 -27
- package/lib/voice/softphone.js +62 -17
- package/lib/web-rtc-client.js +66 -57
- package/package.json +1 -1
- package/src/__tests__/__voice__/call.test.ts +61 -3
- package/src/api/auth.ts +5 -0
- package/src/checker/checks/webrtc-transport.ts +2 -2
- package/src/checker/checks/webrtc.ts +2 -2
- package/src/domain/CallLog.ts +2 -3
- package/src/domain/CallSession.ts +3 -3
- package/src/domain/Phone/CTIPhone.ts +3 -2
- package/src/domain/Phone/WebRTCPhone.ts +87 -85
- package/src/domain/__tests__/CallSession.test.ts +4 -0
- package/src/domain/types.ts +0 -5
- package/src/index.ts +4 -1
- package/src/lib/WazoSessionDescriptionHandler.ts +3 -3
- package/src/service/CallApi.ts +7 -7
- package/src/simple/Auth.ts +15 -3
- package/src/simple/Phone.ts +11 -11
- package/src/simple/index.ts +2 -1
- package/src/simple/room/Room.ts +3 -3
- package/src/simple/room/SipRoom.ts +3 -3
- package/src/state-machine/call-state-machine.ts +38 -19
- package/src/state-machine/softphone-state-machine.ts +1 -2
- package/src/state-machine/utils.ts +12 -4
- package/src/utils/sdp.ts +18 -11
- package/src/utils/update-from.ts +2 -2
- package/src/utils/webrtc.ts +11 -4
- package/src/voice/call.ts +109 -36
- package/src/voice/softphone.ts +75 -18
- 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, {
|
|
17
|
-
import { getCallDisplayName, getCallNumber,
|
|
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
|
|
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.
|
|
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 = {
|
|
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 = {
|
|
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.
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
package/src/voice/softphone.ts
CHANGED
|
@@ -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
|
|
128
|
+
await this.hangupAllCalls();
|
|
118
129
|
|
|
119
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|