@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.
- package/dist/wazo-sdk.js +6 -6
- package/esm/__tests__/__voice__/call.test.js +45 -2
- package/esm/domain/Phone/CTIPhone.js +2 -2
- package/esm/domain/Phone/WebRTCPhone.js +6 -7
- package/esm/index.js +1 -1
- package/esm/service/CallApi.js +4 -4
- package/esm/simple/Phone.js +1 -0
- package/esm/state-machine/call-state-machine.js +1 -1
- package/esm/types/domain/Phone/CTIPhone.d.ts.map +1 -1
- package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/esm/types/index.d.ts +1 -1
- package/esm/types/index.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/Phone.d.ts.map +1 -1
- package/esm/types/state-machine/softphone-state-machine.d.ts +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 +2 -0
- package/esm/types/utils/sdp.d.ts.map +1 -1
- package/esm/types/utils/webrtc.d.ts +1 -0
- package/esm/types/utils/webrtc.d.ts.map +1 -1
- package/esm/types/voice/call.d.ts +27 -12
- package/esm/types/voice/call.d.ts.map +1 -1
- package/esm/types/voice/softphone.d.ts +22 -13
- package/esm/types/voice/softphone.d.ts.map +1 -1
- package/esm/utils/sdp.js +13 -0
- package/esm/utils/webrtc.js +20 -0
- package/esm/voice/call.js +115 -33
- package/esm/voice/softphone.js +105 -25
- package/lib/__tests__/__voice__/call.test.js +44 -1
- package/lib/domain/Phone/CTIPhone.js +2 -2
- package/lib/domain/Phone/WebRTCPhone.js +5 -6
- package/lib/index.js +2 -2
- package/lib/service/CallApi.js +2 -2
- package/lib/simple/Phone.js +1 -0
- package/lib/state-machine/call-state-machine.js +1 -1
- package/lib/types/domain/Phone/CTIPhone.d.ts.map +1 -1
- package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/lib/types/index.d.ts +1 -1
- package/lib/types/index.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/Phone.d.ts.map +1 -1
- package/lib/types/state-machine/softphone-state-machine.d.ts +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 +2 -0
- package/lib/types/utils/sdp.d.ts.map +1 -1
- package/lib/types/utils/webrtc.d.ts +1 -0
- package/lib/types/utils/webrtc.d.ts.map +1 -1
- package/lib/types/voice/call.d.ts +27 -12
- package/lib/types/voice/call.d.ts.map +1 -1
- package/lib/types/voice/softphone.d.ts +22 -13
- package/lib/types/voice/softphone.d.ts.map +1 -1
- package/lib/utils/sdp.js +16 -1
- package/lib/utils/webrtc.js +22 -1
- package/lib/voice/call.js +112 -31
- package/lib/voice/softphone.js +109 -25
- package/package.json +1 -1
- package/src/__tests__/__voice__/call.test.ts +48 -2
- package/src/domain/Phone/CTIPhone.ts +3 -2
- package/src/domain/Phone/WebRTCPhone.ts +9 -9
- package/src/index.ts +1 -1
- package/src/service/CallApi.ts +7 -7
- package/src/simple/Phone.ts +1 -0
- package/src/state-machine/call-state-machine.ts +1 -1
- package/src/state-machine/utils.ts +2 -2
- package/src/utils/sdp.ts +16 -0
- package/src/utils/webrtc.ts +23 -1
- package/src/voice/call.ts +149 -43
- package/src/voice/softphone.ts +130 -31
- package/src/web-rtc-client.ts +1 -1
package/lib/voice/call.js
CHANGED
|
@@ -35,20 +35,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.MESSAGE_TRACK_UPDATED = exports.MESSAGE_TYPE_SIGNAL = exports.MESSAGE_TYPE_CHAT = exports.
|
|
38
|
+
exports.MESSAGE_TRACK_UPDATED = exports.MESSAGE_TYPE_SIGNAL = exports.MESSAGE_TYPE_CHAT = exports.EVENT_CAMERA_DISABLED = exports.EVENT_CAMERA_RESUMED = exports.EVENT_WEB_RTC_STATS = exports.EVENT_SHARE_SCREEN_ENDED = exports.EVENT_SHARE_SCREEN_STARTED = exports.EVENT_SHARE_SCREEN_ENDING = exports.EVENT_REMOVE_STREAM = exports.EVENT_VIDEO_STREAM = exports.EVENT_AUDIO_STREAM = exports.EVENT_TRACK = exports.EVENT_REINVITE = exports.EVENT_CALL_RESUMED = exports.EVENT_CALL_HELD = exports.EVENT_CALL_UN_MUTED = exports.EVENT_CALL_MUTED = exports.EVENT_CALL_ENDED = exports.EVENT_CALL_TERMINATING = exports.EVENT_CALL_REJECTED = exports.EVENT_CALL_ERROR = exports.EVENT_EARLY_MEDIA = exports.EVENT_PROGRESS = exports.EVENT_CALL_CANCELED = exports.EVENT_CALL_LOCALLY_ACCEPTED = exports.EVENT_CALL_REMOTLY_ACCEPTED = void 0;
|
|
39
39
|
/* eslint-disable no-underscore-dangle */
|
|
40
40
|
const events_1 = require("events");
|
|
41
41
|
const xstate_1 = require("xstate");
|
|
42
42
|
const invitation_1 = require("sip.js/lib/api/invitation");
|
|
43
43
|
const session_state_1 = require("sip.js/lib/api/session-state");
|
|
44
44
|
const call_state_machine_1 = __importStar(require("../state-machine/call-state-machine"));
|
|
45
|
+
const new_from_1 = __importDefault(require("../utils/new-from"));
|
|
46
|
+
const update_from_1 = __importDefault(require("../utils/update-from"));
|
|
45
47
|
const IssueReporter_1 = __importDefault(require("../service/IssueReporter"));
|
|
48
|
+
const softphone_1 = __importStar(require("./softphone"));
|
|
46
49
|
const sdp_1 = require("../utils/sdp");
|
|
47
50
|
const utils_1 = require("../state-machine/utils");
|
|
48
51
|
const webrtc_1 = require("../utils/webrtc");
|
|
49
52
|
const logger = IssueReporter_1.default.loggerFor('call');
|
|
50
53
|
// Events
|
|
51
|
-
exports.
|
|
54
|
+
exports.EVENT_CALL_REMOTLY_ACCEPTED = 'remotelyAccepted';
|
|
55
|
+
exports.EVENT_CALL_LOCALLY_ACCEPTED = 'locallyAccepted';
|
|
52
56
|
exports.EVENT_CALL_CANCELED = 'canceled';
|
|
53
57
|
exports.EVENT_PROGRESS = 'progress';
|
|
54
58
|
exports.EVENT_EARLY_MEDIA = 'earlyMedia';
|
|
@@ -71,19 +75,66 @@ exports.EVENT_SHARE_SCREEN_ENDED = 'onScreenShareEnded';
|
|
|
71
75
|
exports.EVENT_WEB_RTC_STATS = 'webRtcStats';
|
|
72
76
|
exports.EVENT_CAMERA_RESUMED = 'onCameraResumed';
|
|
73
77
|
exports.EVENT_CAMERA_DISABLED = 'onCameraDisabled';
|
|
74
|
-
exports.EVENT_TERMINATE_SOUND = 'terminateSound';
|
|
75
78
|
exports.MESSAGE_TYPE_CHAT = 'message/TYPE_CHAT';
|
|
76
79
|
exports.MESSAGE_TYPE_SIGNAL = 'message/TYPE_SIGNAL';
|
|
77
80
|
exports.MESSAGE_TRACK_UPDATED = 'onTrackUpdated';
|
|
78
81
|
class Call extends events_1.EventEmitter {
|
|
82
|
+
static newFrom(call) {
|
|
83
|
+
return (0, new_from_1.default)(call, Call);
|
|
84
|
+
}
|
|
85
|
+
static parseCall(apiCall) {
|
|
86
|
+
const sipCall = {
|
|
87
|
+
id: apiCall.sipCallId,
|
|
88
|
+
stateChange: {
|
|
89
|
+
addListener: () => { },
|
|
90
|
+
},
|
|
91
|
+
remoteIdentity: {
|
|
92
|
+
uri: {
|
|
93
|
+
_normal: {
|
|
94
|
+
user: apiCall.calleeNumber,
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
// Mock sdk so `hasLocalVideo` will return true.
|
|
99
|
+
sessionDescriptionHandler: {
|
|
100
|
+
localMediaStream: {
|
|
101
|
+
getTracks: () => (apiCall.isVideo ? [{ kind: 'video', readyState: 'live' }] : []),
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
const call = new Call(sipCall, softphone_1.default);
|
|
106
|
+
// call._sendAction(Actions.ES);
|
|
107
|
+
call.apiId = apiCall.id;
|
|
108
|
+
call.answerTime = new Date(+apiCall.startingTime);
|
|
109
|
+
// Update call state
|
|
110
|
+
const snapshot = call.callActor.getSnapshot();
|
|
111
|
+
if (apiCall.isUp()) {
|
|
112
|
+
snapshot.value = { [call_state_machine_1.States.ESTABLISHED]: call_state_machine_1.EstablishedStates.ONGOING };
|
|
113
|
+
}
|
|
114
|
+
if (apiCall.muted) {
|
|
115
|
+
snapshot.value = { [call_state_machine_1.States.ESTABLISHED]: call_state_machine_1.EstablishedStates.MUTED };
|
|
116
|
+
}
|
|
117
|
+
if (apiCall.isRinging()) {
|
|
118
|
+
snapshot.value = call_state_machine_1.States.RINGING;
|
|
119
|
+
}
|
|
120
|
+
if (apiCall.isRecording()) {
|
|
121
|
+
call.recording = true;
|
|
122
|
+
}
|
|
123
|
+
// @ts-ignore
|
|
124
|
+
call.callActor.update(snapshot);
|
|
125
|
+
return call;
|
|
126
|
+
}
|
|
79
127
|
constructor(sipCall, phone) {
|
|
80
128
|
super();
|
|
81
129
|
this.shouldSendReinvite = false;
|
|
130
|
+
this.recording = false;
|
|
82
131
|
this.callActor = (0, xstate_1.createActor)(call_state_machine_1.default);
|
|
83
132
|
this.callActor.start();
|
|
84
133
|
this.creationTime = new Date();
|
|
85
134
|
this.phone = phone;
|
|
86
135
|
this.sipCall = sipCall;
|
|
136
|
+
this.number = (0, sdp_1.getCallNumber)(sipCall);
|
|
137
|
+
this.displayName = (0, sdp_1.getCallDisplayName)(sipCall);
|
|
87
138
|
this._bindEvents();
|
|
88
139
|
}
|
|
89
140
|
accept() {
|
|
@@ -96,6 +147,7 @@ class Call extends events_1.EventEmitter {
|
|
|
96
147
|
this.answerTime = new Date();
|
|
97
148
|
return this.phone.client.answer(this.sipCall, options.withCamera).then(() => {
|
|
98
149
|
logger.info('call - accepted', Object.assign({ id: this.id }, options));
|
|
150
|
+
this.emit(exports.EVENT_CALL_LOCALLY_ACCEPTED, this);
|
|
99
151
|
return this.id;
|
|
100
152
|
}).catch(e => {
|
|
101
153
|
logger.error(e);
|
|
@@ -108,7 +160,7 @@ class Call extends events_1.EventEmitter {
|
|
|
108
160
|
return __awaiter(this, void 0, void 0, function* () {
|
|
109
161
|
(0, utils_1.assertCan)(this.callActor, call_state_machine_1.Actions.REJECT);
|
|
110
162
|
logger.info('call - reject', { id: this.id });
|
|
111
|
-
this.
|
|
163
|
+
this.phone.emit(softphone_1.EVENT_TERMINATE_SOUND, 'call rejected locally');
|
|
112
164
|
this.shouldSendReinvite = false;
|
|
113
165
|
return this.hangup();
|
|
114
166
|
});
|
|
@@ -119,7 +171,7 @@ class Call extends events_1.EventEmitter {
|
|
|
119
171
|
logger.info('call - hangup', { id: this.id });
|
|
120
172
|
yield this.phone.client.hangup(this.sipCall);
|
|
121
173
|
// Remove this call from softphone's calls
|
|
122
|
-
this.phone.
|
|
174
|
+
this.phone.onCallTerminated(this);
|
|
123
175
|
return true;
|
|
124
176
|
});
|
|
125
177
|
}
|
|
@@ -132,7 +184,7 @@ class Call extends events_1.EventEmitter {
|
|
|
132
184
|
logger.info('call - force cancel, error', e);
|
|
133
185
|
}
|
|
134
186
|
}
|
|
135
|
-
startConference(withCalls) {
|
|
187
|
+
startConference( /* withCalls: Call[] */) {
|
|
136
188
|
// @TODO
|
|
137
189
|
// const participants = [this, ...withCalls].reduce((acc: Record<string, any>, call: Call) => {
|
|
138
190
|
// acc[call.talkingToIds[0]] = call;
|
|
@@ -150,14 +202,14 @@ class Call extends events_1.EventEmitter {
|
|
|
150
202
|
logger.info('call - mute', { id: this.id });
|
|
151
203
|
this._sendAction(call_state_machine_1.EstablishedActions.MUTE);
|
|
152
204
|
this.phone.client.mute(this.sipCall);
|
|
153
|
-
this.
|
|
205
|
+
this.emit(exports.EVENT_CALL_MUTED);
|
|
154
206
|
}
|
|
155
207
|
unMute() {
|
|
156
208
|
(0, utils_1.assertCan)(this.callActor, call_state_machine_1.EstablishedActions.UN_MUTE);
|
|
157
209
|
logger.info('call - un-mute', { id: this.id });
|
|
158
210
|
this._sendAction(call_state_machine_1.EstablishedActions.UN_MUTE);
|
|
159
211
|
this.phone.client.unmute(this.sipCall);
|
|
160
|
-
this.
|
|
212
|
+
this.emit(exports.EVENT_CALL_UN_MUTED);
|
|
161
213
|
}
|
|
162
214
|
hold() {
|
|
163
215
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -174,7 +226,7 @@ class Call extends events_1.EventEmitter {
|
|
|
174
226
|
yield (0, webrtc_1.downgradeToAudio)(this.sipCall);
|
|
175
227
|
}
|
|
176
228
|
const promise = this.phone.client.hold(this.sipCall, this.isConference(), hasVideo);
|
|
177
|
-
this.
|
|
229
|
+
this.emit(exports.EVENT_CALL_HELD);
|
|
178
230
|
return promise;
|
|
179
231
|
});
|
|
180
232
|
}
|
|
@@ -203,7 +255,7 @@ class Call extends events_1.EventEmitter {
|
|
|
203
255
|
this._onScreenSharing(stream, hadVideo, wasDesktop);
|
|
204
256
|
}
|
|
205
257
|
};
|
|
206
|
-
this.
|
|
258
|
+
this.emit(exports.EVENT_CALL_RESUMED);
|
|
207
259
|
return promise.then(() => {
|
|
208
260
|
const stream = this.getLocalVideoStream();
|
|
209
261
|
if (wasScreensharing) {
|
|
@@ -217,19 +269,37 @@ class Call extends events_1.EventEmitter {
|
|
|
217
269
|
(0, utils_1.assertState)(this.callActor, call_state_machine_1.States.ESTABLISHED);
|
|
218
270
|
logger.info('call - turn camera on', { id: this.id });
|
|
219
271
|
(0, webrtc_1.toggleVideo)(this.sipCall, true);
|
|
220
|
-
this.
|
|
272
|
+
this.emit(exports.EVENT_CAMERA_RESUMED);
|
|
221
273
|
}
|
|
222
274
|
turnCameraOff() {
|
|
223
275
|
(0, utils_1.assertState)(this.callActor, call_state_machine_1.States.ESTABLISHED);
|
|
224
276
|
logger.info('call - turn camera off', { id: this.id });
|
|
225
277
|
(0, webrtc_1.toggleVideo)(this.sipCall, false);
|
|
226
|
-
this.
|
|
278
|
+
this.emit(exports.EVENT_CAMERA_DISABLED);
|
|
279
|
+
}
|
|
280
|
+
isMuted() {
|
|
281
|
+
return this._hasEstablishedState(call_state_machine_1.EstablishedStates.MUTED);
|
|
282
|
+
}
|
|
283
|
+
isCameraMuted() {
|
|
284
|
+
return (0, webrtc_1.isVideoMuted)(this.sipCall);
|
|
227
285
|
}
|
|
228
286
|
isHeld() {
|
|
229
|
-
return
|
|
287
|
+
return this._hasEstablishedState(call_state_machine_1.EstablishedStates.HELD);
|
|
288
|
+
}
|
|
289
|
+
isEstablishing() {
|
|
290
|
+
return this.sipCall.state === session_state_1.SessionState.Establishing;
|
|
230
291
|
}
|
|
231
292
|
isEstablished() {
|
|
232
|
-
return
|
|
293
|
+
return call_state_machine_1.States.ESTABLISHED in this.state;
|
|
294
|
+
}
|
|
295
|
+
isRinging() {
|
|
296
|
+
return this.state === call_state_machine_1.States.RINGING;
|
|
297
|
+
}
|
|
298
|
+
isOutgoing() {
|
|
299
|
+
return this.sipCall instanceof invitation_1.Invitation;
|
|
300
|
+
}
|
|
301
|
+
isIncoming() {
|
|
302
|
+
return !this.isOutgoing();
|
|
233
303
|
}
|
|
234
304
|
isConference() {
|
|
235
305
|
var _a;
|
|
@@ -238,7 +308,7 @@ class Call extends events_1.EventEmitter {
|
|
|
238
308
|
transfer(target) {
|
|
239
309
|
(0, utils_1.assertState)(this.callActor, call_state_machine_1.States.ESTABLISHED);
|
|
240
310
|
logger.info('call - transfer', { id: this.id, target });
|
|
241
|
-
this.phone.client.transfer(this.sipCall, target);
|
|
311
|
+
return this.phone.client.transfer(this.sipCall, target);
|
|
242
312
|
}
|
|
243
313
|
atxfer() {
|
|
244
314
|
(0, utils_1.assertState)(this.callActor, call_state_machine_1.States.ESTABLISHED);
|
|
@@ -366,7 +436,7 @@ class Call extends events_1.EventEmitter {
|
|
|
366
436
|
catch (e) {
|
|
367
437
|
console.warn(e);
|
|
368
438
|
}
|
|
369
|
-
this.
|
|
439
|
+
this.emit(exports.EVENT_SHARE_SCREEN_ENDED);
|
|
370
440
|
this.currentScreenShare = null;
|
|
371
441
|
return reinvited;
|
|
372
442
|
});
|
|
@@ -378,7 +448,7 @@ class Call extends events_1.EventEmitter {
|
|
|
378
448
|
}
|
|
379
449
|
onAccepted() {
|
|
380
450
|
this._sendAction(call_state_machine_1.Actions.REMOTLY_ACCEPTED);
|
|
381
|
-
this.emit(exports.
|
|
451
|
+
this.emit(exports.EVENT_CALL_REMOTLY_ACCEPTED);
|
|
382
452
|
}
|
|
383
453
|
onProgress() {
|
|
384
454
|
this.emit(exports.EVENT_PROGRESS);
|
|
@@ -392,10 +462,13 @@ class Call extends events_1.EventEmitter {
|
|
|
392
462
|
onError(error) {
|
|
393
463
|
this.emit(exports.EVENT_CALL_ERROR, error);
|
|
394
464
|
}
|
|
465
|
+
onCallIncoming() {
|
|
466
|
+
this._sendAction(call_state_machine_1.Actions.INCOMING_CALL);
|
|
467
|
+
}
|
|
395
468
|
onRejected() {
|
|
396
469
|
this.endTime = new Date();
|
|
397
470
|
this.emit(exports.EVENT_CALL_REJECTED);
|
|
398
|
-
this.phone.
|
|
471
|
+
this.phone.onCallTerminated(this);
|
|
399
472
|
}
|
|
400
473
|
onNetworkStats(stats, previousStats) {
|
|
401
474
|
this.emit(exports.EVENT_WEB_RTC_STATS, stats, previousStats);
|
|
@@ -427,13 +500,21 @@ class Call extends events_1.EventEmitter {
|
|
|
427
500
|
onReinvite(request, updatedCalleeName, updatedNumber, hadRemoteVideo) {
|
|
428
501
|
this.emit(exports.EVENT_REINVITE, request, updatedCalleeName, updatedNumber, hadRemoteVideo);
|
|
429
502
|
}
|
|
503
|
+
updateFrom(call) {
|
|
504
|
+
(0, update_from_1.default)(this, call);
|
|
505
|
+
}
|
|
506
|
+
is(otherCall) {
|
|
507
|
+
return otherCall && this.id && this.id === otherCall.id;
|
|
508
|
+
}
|
|
430
509
|
get id() {
|
|
431
510
|
return (0, sdp_1.getSipSessionId)(this.sipCall);
|
|
432
511
|
}
|
|
433
|
-
get
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
512
|
+
get state() {
|
|
513
|
+
return (0, utils_1.getState)(this.callActor);
|
|
514
|
+
}
|
|
515
|
+
_hasEstablishedState(establishedState) {
|
|
516
|
+
const state = this.state;
|
|
517
|
+
return call_state_machine_1.States.ESTABLISHED in state && state[call_state_machine_1.States.ESTABLISHED] === establishedState;
|
|
437
518
|
}
|
|
438
519
|
_bindEvents() {
|
|
439
520
|
if (this.sipCall instanceof invitation_1.Invitation) {
|
|
@@ -443,21 +524,22 @@ class Call extends events_1.EventEmitter {
|
|
|
443
524
|
logger.trace('call - on sip session canceled', { callId: message.callId, id: this.id });
|
|
444
525
|
onCancel(message);
|
|
445
526
|
const elsewhere = message.data.indexOf('cause=26') !== -1 && message.data.indexOf('completed elsewhere') !== -1;
|
|
446
|
-
this.
|
|
527
|
+
this.emit(exports.EVENT_CALL_CANCELED, elsewhere);
|
|
447
528
|
};
|
|
448
529
|
}
|
|
449
530
|
this.sipCall.stateChange.addListener((newState) => {
|
|
450
531
|
switch (newState) {
|
|
451
532
|
case session_state_1.SessionState.Terminating:
|
|
452
533
|
logger.info('call - terminating', { id: this.id });
|
|
453
|
-
this.
|
|
534
|
+
this.emit(exports.EVENT_CALL_TERMINATING);
|
|
454
535
|
break;
|
|
455
536
|
case session_state_1.SessionState.Terminated:
|
|
456
537
|
{
|
|
457
538
|
logger.info('call - terminated', { id: this.id });
|
|
458
539
|
// Should be called before `_onCallTerminated` or the callCount will not decrement...
|
|
459
540
|
this.endTime = new Date();
|
|
460
|
-
|
|
541
|
+
this.emit(exports.EVENT_CALL_ENDED);
|
|
542
|
+
return this.phone.onCallTerminated(this);
|
|
461
543
|
}
|
|
462
544
|
default:
|
|
463
545
|
break;
|
|
@@ -484,18 +566,17 @@ class Call extends events_1.EventEmitter {
|
|
|
484
566
|
kind,
|
|
485
567
|
});
|
|
486
568
|
if (kind === 'audio') {
|
|
487
|
-
return this.
|
|
569
|
+
return this.emit(exports.EVENT_AUDIO_STREAM, stream);
|
|
488
570
|
}
|
|
489
|
-
return this.
|
|
571
|
+
return this.emit(exports.EVENT_VIDEO_STREAM, stream, event.track.id, event);
|
|
490
572
|
};
|
|
491
573
|
peerConnection.onremovestream = (event) => {
|
|
492
574
|
logger.info('call - on remove stream called on the peer connection', {
|
|
493
575
|
id: event.stream.id,
|
|
494
576
|
tracks: event.stream.getTracks(),
|
|
495
577
|
});
|
|
496
|
-
this.
|
|
578
|
+
this.emit(exports.EVENT_REMOVE_STREAM, event.stream);
|
|
497
579
|
};
|
|
498
|
-
// Client events
|
|
499
580
|
}
|
|
500
581
|
_downgradeToAudio(withMessage = true) {
|
|
501
582
|
(0, utils_1.assertState)(this.callActor, call_state_machine_1.States.ESTABLISHED);
|
|
@@ -558,7 +639,7 @@ class Call extends events_1.EventEmitter {
|
|
|
558
639
|
if (screenTrack) {
|
|
559
640
|
screenTrack.onended = () => {
|
|
560
641
|
logger.info('call - on screenshare ended', { hadVideo, id: this.id, screenTrack });
|
|
561
|
-
this.
|
|
642
|
+
this.emit(exports.EVENT_SHARE_SCREEN_ENDING);
|
|
562
643
|
};
|
|
563
644
|
}
|
|
564
645
|
this.currentScreenShare = {
|
|
@@ -568,7 +649,7 @@ class Call extends events_1.EventEmitter {
|
|
|
568
649
|
desktop,
|
|
569
650
|
};
|
|
570
651
|
(0, webrtc_1.setLocalMediaStream)(this.sipCall, screenStream);
|
|
571
|
-
this.
|
|
652
|
+
this.emit(exports.EVENT_SHARE_SCREEN_STARTED, screenStream);
|
|
572
653
|
}
|
|
573
654
|
_sendAction(action) {
|
|
574
655
|
this.callActor.send({ type: action });
|
package/lib/voice/softphone.js
CHANGED
|
@@ -35,26 +35,30 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
35
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
36
|
};
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
-
exports.Softphone = exports.MESSAGE_TYPE_SIGNAL = exports.MESSAGE_TYPE_CHAT = exports.EVENT_ON_SIGNAL = exports.EVENT_ON_CHAT = exports.EVENT_ON_MESSAGE = exports.EVENT_VIDEO_INPUT_CHANGE = exports.EVENT_DISCONNECTED = exports.EVENT_UNREGISTERED = exports.EVENT_REGISTERED = exports.EVENT_HEARTBEAT_TIMEOUT = exports.EVENT_HEARTBEAT = exports.EVENT_INCOMING = void 0;
|
|
38
|
+
exports.Softphone = exports.MESSAGE_TYPE_SIGNAL = exports.MESSAGE_TYPE_CHAT = exports.EVENT_ON_SIGNAL = exports.EVENT_ON_CHAT = exports.EVENT_ON_MESSAGE = exports.EVENT_PLAY_INBOUND_CALL_SOUND = exports.EVENT_PLAY_RING_SOUND = exports.EVENT_PLAY_HANGUP_SOUND = exports.EVENT_TERMINATE_SOUND = exports.EVENT_VIDEO_INPUT_CHANGE = exports.EVENT_DISCONNECTED = exports.EVENT_UNREGISTERED = exports.EVENT_REGISTERED = exports.EVENT_HEARTBEAT_TIMEOUT = exports.EVENT_HEARTBEAT = exports.EVENT_OUTGOING = exports.EVENT_INCOMING = void 0;
|
|
39
39
|
/* eslint-disable no-underscore-dangle */
|
|
40
40
|
const events_1 = require("events");
|
|
41
41
|
const xstate_1 = require("xstate");
|
|
42
42
|
const InvalidState_1 = __importDefault(require("../domain/InvalidState"));
|
|
43
43
|
const __1 = __importDefault(require(".."));
|
|
44
44
|
const softphone_state_machine_1 = __importStar(require("../state-machine/softphone-state-machine"));
|
|
45
|
-
const call_state_machine_1 = require("../state-machine/call-state-machine");
|
|
46
45
|
const web_rtc_client_1 = __importDefault(require("../web-rtc-client"));
|
|
47
46
|
const IssueReporter_1 = __importDefault(require("../service/IssueReporter"));
|
|
48
47
|
const sip_logger_1 = __importDefault(require("../utils/sip-logger"));
|
|
49
48
|
const call_1 = __importDefault(require("./call"));
|
|
50
49
|
const utils_1 = require("../state-machine/utils");
|
|
51
50
|
exports.EVENT_INCOMING = 'incoming';
|
|
51
|
+
exports.EVENT_OUTGOING = 'outgoing';
|
|
52
52
|
exports.EVENT_HEARTBEAT = 'heartbeat';
|
|
53
53
|
exports.EVENT_HEARTBEAT_TIMEOUT = 'heartbeatTimeout';
|
|
54
54
|
exports.EVENT_REGISTERED = 'registered';
|
|
55
55
|
exports.EVENT_UNREGISTERED = 'unregistered';
|
|
56
56
|
exports.EVENT_DISCONNECTED = 'disconnected';
|
|
57
57
|
exports.EVENT_VIDEO_INPUT_CHANGE = 'videoInputChange';
|
|
58
|
+
exports.EVENT_TERMINATE_SOUND = 'terminateSound';
|
|
59
|
+
exports.EVENT_PLAY_HANGUP_SOUND = 'playHangupSound';
|
|
60
|
+
exports.EVENT_PLAY_RING_SOUND = 'playRingSound';
|
|
61
|
+
exports.EVENT_PLAY_INBOUND_CALL_SOUND = 'playInboundCallSound';
|
|
58
62
|
exports.EVENT_ON_MESSAGE = 'message';
|
|
59
63
|
exports.EVENT_ON_CHAT = 'phone/ON_CHAT';
|
|
60
64
|
exports.EVENT_ON_SIGNAL = 'phone/ON_SIGNAL';
|
|
@@ -64,6 +68,7 @@ const logger = IssueReporter_1.default.loggerFor('softphone');
|
|
|
64
68
|
class Softphone extends events_1.EventEmitter {
|
|
65
69
|
constructor() {
|
|
66
70
|
super();
|
|
71
|
+
this.ringingEnabled = true;
|
|
67
72
|
this.calls = [];
|
|
68
73
|
this.softphoneActor = (0, xstate_1.createActor)(softphone_state_machine_1.default);
|
|
69
74
|
this.softphoneActor.start();
|
|
@@ -86,6 +91,11 @@ class Softphone extends events_1.EventEmitter {
|
|
|
86
91
|
this.options.userUuid = session.uuid || '';
|
|
87
92
|
return this.connectWithCredentials(server, this.sipLine, session.displayName(), options);
|
|
88
93
|
}
|
|
94
|
+
hangupAllCalls() {
|
|
95
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
96
|
+
return Promise.all(this.calls.map(call => call.hangup()));
|
|
97
|
+
});
|
|
98
|
+
}
|
|
89
99
|
disconnect() {
|
|
90
100
|
return __awaiter(this, void 0, void 0, function* () {
|
|
91
101
|
(0, utils_1.assertCan)(this.softphoneActor, softphone_state_machine_1.Actions.UNREGISTER);
|
|
@@ -93,7 +103,7 @@ class Softphone extends events_1.EventEmitter {
|
|
|
93
103
|
return null;
|
|
94
104
|
}
|
|
95
105
|
// Terminate all calls
|
|
96
|
-
yield
|
|
106
|
+
yield this.hangupAllCalls();
|
|
97
107
|
return this.client.unregister();
|
|
98
108
|
});
|
|
99
109
|
}
|
|
@@ -138,19 +148,20 @@ class Softphone extends events_1.EventEmitter {
|
|
|
138
148
|
logger.warn('softphone - calling with empty number, bailing.');
|
|
139
149
|
return Promise.resolve(null);
|
|
140
150
|
}
|
|
141
|
-
if (this.
|
|
142
|
-
throw new InvalidState_1.default(`Invalid state ${softphone_state_machine_1.States.UNREGISTERED}`, softphone_state_machine_1.States.UNREGISTERED);
|
|
143
|
-
}
|
|
144
|
-
if (this.getState() !== softphone_state_machine_1.States.REGISTERING) {
|
|
151
|
+
if (this.state === softphone_state_machine_1.States.REGISTERING) {
|
|
145
152
|
logger.info('softphone - calling when registering, waiting for Softphone to be registered ...');
|
|
146
153
|
yield (0, utils_1.waitUntilState)(this.softphoneActor, softphone_state_machine_1.States.REGISTERED);
|
|
147
154
|
}
|
|
155
|
+
if (this.state !== softphone_state_machine_1.States.UNREGISTERED) {
|
|
156
|
+
throw new InvalidState_1.default(`Invalid state ${softphone_state_machine_1.States.UNREGISTERED}`, softphone_state_machine_1.States.UNREGISTERED);
|
|
157
|
+
}
|
|
148
158
|
// Hold other calls
|
|
149
159
|
this.holdAllCalls();
|
|
150
160
|
let sipCall;
|
|
151
161
|
try {
|
|
152
162
|
sipCall = this.client.call(options.params.To, options.withCamera, options.audioOnly, options.conference, options);
|
|
153
163
|
const call = new call_1.default(sipCall, this);
|
|
164
|
+
this.emit(exports.EVENT_OUTGOING, call);
|
|
154
165
|
return call;
|
|
155
166
|
}
|
|
156
167
|
catch (error) {
|
|
@@ -162,6 +173,58 @@ class Softphone extends events_1.EventEmitter {
|
|
|
162
173
|
removeCall(callToRemove) {
|
|
163
174
|
this.calls = this.calls.filter((call) => call.id !== callToRemove.id);
|
|
164
175
|
}
|
|
176
|
+
onCallTerminated(call) {
|
|
177
|
+
logger.info('softphone - on call terminated', { id: call.id });
|
|
178
|
+
const currentCall = this.getCurrentCall();
|
|
179
|
+
const isCurrentCall = (currentCall === null || currentCall === void 0 ? void 0 : currentCall.id) === call.id;
|
|
180
|
+
const isRinging = call.isRinging();
|
|
181
|
+
const isCurrentIncomingCall = isCurrentCall && isRinging;
|
|
182
|
+
const incomingCalls = this.getIncomingCalls();
|
|
183
|
+
const nbIncomingCalls = incomingCalls.length;
|
|
184
|
+
// Send an event to terminate ringing sound
|
|
185
|
+
if (isCurrentIncomingCall) {
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
// Avoid race condition when the other is calling and hanging up immediately
|
|
188
|
+
this.emit(exports.EVENT_TERMINATE_SOUND, call, 'call terminated');
|
|
189
|
+
}, 5);
|
|
190
|
+
}
|
|
191
|
+
// If the terminated call was an incoming call, we have to re-trigger if it's was the first incoming call
|
|
192
|
+
// Otherwise, we have to re-trigger an incoming call event if another call is incoming
|
|
193
|
+
const shouldRetrigger = isCurrentCall ? nbIncomingCalls : isCurrentIncomingCall;
|
|
194
|
+
// Re-trigger incoming call event for remaining incoming calls
|
|
195
|
+
logger.info('Softphone - check to re-trigger incoming call', { shouldRetrigger, nbIncomingCalls });
|
|
196
|
+
if (shouldRetrigger && nbIncomingCalls > 1) {
|
|
197
|
+
const nextCall = incomingCalls.find(c => c.id !== call.id);
|
|
198
|
+
// Avoid race condition
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
if (this.ringingEnabled) {
|
|
201
|
+
if (currentCall) {
|
|
202
|
+
this.emit(exports.EVENT_PLAY_RING_SOUND, this.audioRingDeviceId, this.audioRingVolume, nextCall);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.emit(exports.EVENT_PLAY_INBOUND_CALL_SOUND, this.audioOutputDeviceId, this.audioOutputVolume, nextCall);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
this.emit(exports.EVENT_INCOMING, nextCall);
|
|
209
|
+
}, 100);
|
|
210
|
+
}
|
|
211
|
+
// @ts-ignore: does not exist
|
|
212
|
+
if (!call.sipCall.isCanceled) {
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
this.emit(exports.EVENT_PLAY_HANGUP_SOUND, this.audioOutputDeviceId, this.audioOutputVolume, call);
|
|
215
|
+
}, 10);
|
|
216
|
+
}
|
|
217
|
+
this.client.onCallEnded(call.sipCall);
|
|
218
|
+
this.removeCall(call);
|
|
219
|
+
}
|
|
220
|
+
enableRinging() {
|
|
221
|
+
logger.info('softphone - enable ringing');
|
|
222
|
+
this.ringingEnabled = true;
|
|
223
|
+
}
|
|
224
|
+
disableRinging() {
|
|
225
|
+
logger.info('softphone - disable ringing');
|
|
226
|
+
this.ringingEnabled = false;
|
|
227
|
+
}
|
|
165
228
|
holdAllCalls(exceptCall = null) {
|
|
166
229
|
this.calls.filter(call => !call.isHeld() && call.id === (exceptCall === null || exceptCall === void 0 ? void 0 : exceptCall.id)).forEach(call => call.hold());
|
|
167
230
|
}
|
|
@@ -216,25 +279,44 @@ class Softphone extends events_1.EventEmitter {
|
|
|
216
279
|
logger.info('softphone - changing ring volume', { volume });
|
|
217
280
|
this.audioRingVolume = volume;
|
|
218
281
|
}
|
|
219
|
-
getCalls() {
|
|
220
|
-
return this.calls;
|
|
221
|
-
}
|
|
222
282
|
getCurrentCall() {
|
|
223
|
-
return this.
|
|
283
|
+
return this.getActiveCalls()[0];
|
|
284
|
+
}
|
|
285
|
+
getActiveCalls() {
|
|
286
|
+
return this.calls.filter(call => call.isEstablished());
|
|
287
|
+
}
|
|
288
|
+
getIncomingCalls() {
|
|
289
|
+
return this.calls.filter(call => call.isRinging());
|
|
224
290
|
}
|
|
225
|
-
|
|
226
|
-
return this.softphoneActor
|
|
291
|
+
get state() {
|
|
292
|
+
return (0, utils_1.getState)(this.softphoneActor);
|
|
293
|
+
}
|
|
294
|
+
get user() {
|
|
295
|
+
var _a, _b, _c, _d;
|
|
296
|
+
return (_d = (_c = (_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.userAgent) === null || _b === void 0 ? void 0 : _b.contact) === null || _c === void 0 ? void 0 : _c.uri) === null || _d === void 0 ? void 0 : _d.user;
|
|
227
297
|
}
|
|
228
298
|
getUserAgent() {
|
|
229
299
|
var _a, _b;
|
|
230
300
|
return ((_b = (_a = this.client) === null || _a === void 0 ? void 0 : _a.config) === null || _b === void 0 ? void 0 : _b.userAgentString) || 'softphone';
|
|
231
301
|
}
|
|
302
|
+
isRegistered() {
|
|
303
|
+
return this.state === softphone_state_machine_1.States.REGISTERED;
|
|
304
|
+
}
|
|
232
305
|
_bindEvents() {
|
|
233
306
|
this.client.on(this.client.INVITE, (sipSession) => {
|
|
234
307
|
const call = new call_1.default(sipSession, this);
|
|
235
|
-
|
|
308
|
+
logger.info('softphone - on invite', { callId: call.id, number: call.number });
|
|
309
|
+
call.onCallIncoming();
|
|
236
310
|
this.calls.push(call);
|
|
237
|
-
this.
|
|
311
|
+
this.emit(exports.EVENT_INCOMING, call);
|
|
312
|
+
if (!this.getCurrentCall()) {
|
|
313
|
+
if (this.ringingEnabled) {
|
|
314
|
+
this.emit(exports.EVENT_PLAY_RING_SOUND, this.audioRingDeviceId, this.audioRingVolume, call);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
this.emit(exports.EVENT_PLAY_INBOUND_CALL_SOUND, this.audioOutputDeviceId, this.audioOutputVolume, call);
|
|
319
|
+
}
|
|
238
320
|
});
|
|
239
321
|
this.client.on(this.client.ON_REINVITE, (sipCall, request, updatedCalleeName, updatedNumber, hadRemoteVideo) => {
|
|
240
322
|
logger.info('softphone - on reinvite', {
|
|
@@ -247,6 +329,8 @@ class Softphone extends events_1.EventEmitter {
|
|
|
247
329
|
this._onCallEvent(sipCall, 'onReinvite', request, updatedCalleeName, updatedNumber, hadRemoteVideo);
|
|
248
330
|
});
|
|
249
331
|
this.client.on(this.client.ACCEPTED, (sipCall) => __awaiter(this, void 0, void 0, function* () {
|
|
332
|
+
const call = this._getCall(sipCall);
|
|
333
|
+
this.emit(exports.EVENT_TERMINATE_SOUND, call, 'call accepted');
|
|
250
334
|
this._onCallEvent(sipCall, 'onAccepted');
|
|
251
335
|
if (this.audioOutputDeviceId) {
|
|
252
336
|
yield this.client.changeAudioOutputDevice(this.audioOutputDeviceId);
|
|
@@ -276,12 +360,12 @@ class Softphone extends events_1.EventEmitter {
|
|
|
276
360
|
});
|
|
277
361
|
this.client.on(this.client.UNREGISTERED, () => {
|
|
278
362
|
logger.info('softphone - unregistered');
|
|
279
|
-
this.
|
|
363
|
+
this.emit(exports.EVENT_UNREGISTERED);
|
|
280
364
|
this._sendAction(softphone_state_machine_1.Actions.UNREGISTERED);
|
|
281
365
|
});
|
|
282
366
|
this.client.on(this.client.ON_DISCONNECTED, () => {
|
|
283
367
|
logger.info('softphone - disconnected');
|
|
284
|
-
this.
|
|
368
|
+
this.emit(exports.EVENT_DISCONNECTED);
|
|
285
369
|
this._sendAction(softphone_state_machine_1.Actions.TRANSPORT_CLOSED);
|
|
286
370
|
});
|
|
287
371
|
this.client.on(this.client.REGISTERED, () => {
|
|
@@ -292,7 +376,7 @@ class Softphone extends events_1.EventEmitter {
|
|
|
292
376
|
}
|
|
293
377
|
this.stopHeartbeat();
|
|
294
378
|
this._sendAction(softphone_state_machine_1.Actions.REGISTER_DONE);
|
|
295
|
-
this.
|
|
379
|
+
this.emit(exports.EVENT_REGISTERED);
|
|
296
380
|
// If the phone registered with a current callSession (eg: when switching network):
|
|
297
381
|
// send a reinvite to renegociate ICE with new IP
|
|
298
382
|
if (this.shouldSendReinvite) {
|
|
@@ -308,7 +392,7 @@ class Softphone extends events_1.EventEmitter {
|
|
|
308
392
|
});
|
|
309
393
|
this.client.on(this.client.DISCONNECTED, () => {
|
|
310
394
|
logger.info('softphone - disconnected');
|
|
311
|
-
this.
|
|
395
|
+
this.emit(exports.EVENT_DISCONNECTED);
|
|
312
396
|
// Do not trigger heartbeat if already running
|
|
313
397
|
if (!this.client.hasHeartbeat()) {
|
|
314
398
|
this.startHeartbeat();
|
|
@@ -317,17 +401,17 @@ class Softphone extends events_1.EventEmitter {
|
|
|
317
401
|
this.shouldSendReinvite = this.calls.length > 0;
|
|
318
402
|
});
|
|
319
403
|
this.client.on('onVideoInputChange', stream => {
|
|
320
|
-
this.
|
|
404
|
+
this.emit(exports.EVENT_VIDEO_INPUT_CHANGE, stream);
|
|
321
405
|
});
|
|
322
406
|
this.client.on(this.client.MESSAGE, (message) => {
|
|
323
407
|
this._onMessage(message);
|
|
324
|
-
this.
|
|
408
|
+
this.emit(exports.EVENT_ON_MESSAGE, message);
|
|
325
409
|
});
|
|
326
410
|
this.client.setOnHeartbeatTimeout(() => {
|
|
327
|
-
this.
|
|
411
|
+
this.emit(exports.EVENT_HEARTBEAT_TIMEOUT);
|
|
328
412
|
});
|
|
329
413
|
this.client.setOnHeartbeatCallback(() => {
|
|
330
|
-
this.
|
|
414
|
+
this.emit(exports.EVENT_HEARTBEAT);
|
|
331
415
|
});
|
|
332
416
|
}
|
|
333
417
|
_sendAction(action) {
|
|
@@ -360,11 +444,11 @@ class Softphone extends events_1.EventEmitter {
|
|
|
360
444
|
const { type, content, } = body;
|
|
361
445
|
switch (type) {
|
|
362
446
|
case exports.MESSAGE_TYPE_CHAT:
|
|
363
|
-
this.
|
|
447
|
+
this.emit(exports.EVENT_ON_CHAT, content);
|
|
364
448
|
break;
|
|
365
449
|
case exports.MESSAGE_TYPE_SIGNAL:
|
|
366
450
|
{
|
|
367
|
-
this.
|
|
451
|
+
this.emit(exports.EVENT_ON_SIGNAL, content);
|
|
368
452
|
break;
|
|
369
453
|
}
|
|
370
454
|
default:
|