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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/dist/wazo-sdk.js +6 -6
  2. package/esm/__tests__/__voice__/call.test.js +45 -2
  3. package/esm/domain/Phone/CTIPhone.js +2 -2
  4. package/esm/domain/Phone/WebRTCPhone.js +6 -7
  5. package/esm/index.js +1 -1
  6. package/esm/service/CallApi.js +4 -4
  7. package/esm/simple/Phone.js +1 -0
  8. package/esm/state-machine/call-state-machine.js +1 -1
  9. package/esm/types/domain/Phone/CTIPhone.d.ts.map +1 -1
  10. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  11. package/esm/types/index.d.ts +1 -1
  12. package/esm/types/index.d.ts.map +1 -1
  13. package/esm/types/service/CallApi.d.ts +5 -5
  14. package/esm/types/service/CallApi.d.ts.map +1 -1
  15. package/esm/types/simple/Phone.d.ts.map +1 -1
  16. package/esm/types/state-machine/softphone-state-machine.d.ts +1 -1
  17. package/esm/types/state-machine/utils.d.ts +1 -1
  18. package/esm/types/state-machine/utils.d.ts.map +1 -1
  19. package/esm/types/utils/sdp.d.ts +2 -0
  20. package/esm/types/utils/sdp.d.ts.map +1 -1
  21. package/esm/types/utils/webrtc.d.ts +1 -0
  22. package/esm/types/utils/webrtc.d.ts.map +1 -1
  23. package/esm/types/voice/call.d.ts +27 -12
  24. package/esm/types/voice/call.d.ts.map +1 -1
  25. package/esm/types/voice/softphone.d.ts +22 -13
  26. package/esm/types/voice/softphone.d.ts.map +1 -1
  27. package/esm/utils/sdp.js +13 -0
  28. package/esm/utils/webrtc.js +20 -0
  29. package/esm/voice/call.js +115 -33
  30. package/esm/voice/softphone.js +105 -25
  31. package/lib/__tests__/__voice__/call.test.js +44 -1
  32. package/lib/domain/Phone/CTIPhone.js +2 -2
  33. package/lib/domain/Phone/WebRTCPhone.js +5 -6
  34. package/lib/index.js +2 -2
  35. package/lib/service/CallApi.js +2 -2
  36. package/lib/simple/Phone.js +1 -0
  37. package/lib/state-machine/call-state-machine.js +1 -1
  38. package/lib/types/domain/Phone/CTIPhone.d.ts.map +1 -1
  39. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  40. package/lib/types/index.d.ts +1 -1
  41. package/lib/types/index.d.ts.map +1 -1
  42. package/lib/types/service/CallApi.d.ts +5 -5
  43. package/lib/types/service/CallApi.d.ts.map +1 -1
  44. package/lib/types/simple/Phone.d.ts.map +1 -1
  45. package/lib/types/state-machine/softphone-state-machine.d.ts +1 -1
  46. package/lib/types/state-machine/utils.d.ts +1 -1
  47. package/lib/types/state-machine/utils.d.ts.map +1 -1
  48. package/lib/types/utils/sdp.d.ts +2 -0
  49. package/lib/types/utils/sdp.d.ts.map +1 -1
  50. package/lib/types/utils/webrtc.d.ts +1 -0
  51. package/lib/types/utils/webrtc.d.ts.map +1 -1
  52. package/lib/types/voice/call.d.ts +27 -12
  53. package/lib/types/voice/call.d.ts.map +1 -1
  54. package/lib/types/voice/softphone.d.ts +22 -13
  55. package/lib/types/voice/softphone.d.ts.map +1 -1
  56. package/lib/utils/sdp.js +16 -1
  57. package/lib/utils/webrtc.js +22 -1
  58. package/lib/voice/call.js +112 -31
  59. package/lib/voice/softphone.js +109 -25
  60. package/package.json +1 -1
  61. package/src/__tests__/__voice__/call.test.ts +48 -2
  62. package/src/domain/Phone/CTIPhone.ts +3 -2
  63. package/src/domain/Phone/WebRTCPhone.ts +9 -9
  64. package/src/index.ts +1 -1
  65. package/src/service/CallApi.ts +7 -7
  66. package/src/simple/Phone.ts +1 -0
  67. package/src/state-machine/call-state-machine.ts +1 -1
  68. package/src/state-machine/utils.ts +2 -2
  69. package/src/utils/sdp.ts +16 -0
  70. package/src/utils/webrtc.ts +23 -1
  71. package/src/voice/call.ts +149 -43
  72. package/src/voice/softphone.ts +130 -31
  73. package/src/web-rtc-client.ts +1 -1
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.EVENT_TERMINATE_SOUND = 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_ACCEPTED = void 0;
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.EVENT_CALL_ACCEPTED = 'accepted';
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.eventEmitter.emit(exports.EVENT_TERMINATE_SOUND, 'call rejected locally');
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.removeCall(this);
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.eventEmitter.emit(exports.EVENT_CALL_MUTED);
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.eventEmitter.emit(exports.EVENT_CALL_UN_MUTED);
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.eventEmitter.emit(exports.EVENT_CALL_HELD);
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.eventEmitter.emit(exports.EVENT_CALL_RESUMED);
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.eventEmitter.emit(exports.EVENT_CAMERA_RESUMED);
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.eventEmitter.emit(exports.EVENT_CAMERA_DISABLED);
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 (0, utils_1.getState)(this.callActor) === call_state_machine_1.EstablishedStates.HELD;
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 (0, utils_1.getState)(this.callActor) === call_state_machine_1.States.ESTABLISHED;
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.eventEmitter.emit(exports.EVENT_SHARE_SCREEN_ENDED);
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.EVENT_CALL_ACCEPTED);
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.removeCall(this);
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 number() {
434
- const identity = this.sipCall.remoteIdentity || this.sipCall.assertedIdentity;
435
- // @ts-ignore: private
436
- return identity ? identity.uri._normal.user : null;
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.eventEmitter.emit(exports.EVENT_CALL_CANCELED, elsewhere);
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.eventEmitter.emit(exports.EVENT_CALL_TERMINATING);
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
- return this.eventEmitter.emit(exports.EVENT_CALL_ENDED);
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.eventEmitter.emit(exports.EVENT_AUDIO_STREAM, stream);
569
+ return this.emit(exports.EVENT_AUDIO_STREAM, stream);
488
570
  }
489
- return this.eventEmitter.emit(exports.EVENT_VIDEO_STREAM, stream, event.track.id, event);
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.eventEmitter.emit(exports.EVENT_REMOVE_STREAM, event.stream);
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.eventEmitter.emit(exports.EVENT_SHARE_SCREEN_ENDING);
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.eventEmitter.emit(exports.EVENT_SHARE_SCREEN_STARTED, screenStream);
652
+ this.emit(exports.EVENT_SHARE_SCREEN_STARTED, screenStream);
572
653
  }
573
654
  _sendAction(action) {
574
655
  this.callActor.send({ type: action });
@@ -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 Promise.all(this.calls.map(call => call.hangup()));
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.getState() !== softphone_state_machine_1.States.UNREGISTERED) {
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.calls.find(call => call.isEstablished());
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
- getState() {
226
- return this.softphoneActor.getSnapshot().value;
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
- call._sendAction(call_state_machine_1.Actions.INCOMING_CALL);
308
+ logger.info('softphone - on invite', { callId: call.id, number: call.number });
309
+ call.onCallIncoming();
236
310
  this.calls.push(call);
237
- this.eventEmitter.emit(exports.EVENT_INCOMING, call);
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.eventEmitter.emit(exports.EVENT_UNREGISTERED);
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.eventEmitter.emit(exports.EVENT_DISCONNECTED);
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.eventEmitter.emit(exports.EVENT_REGISTERED);
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.eventEmitter.emit(exports.EVENT_DISCONNECTED);
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.eventEmitter.emit(exports.EVENT_VIDEO_INPUT_CHANGE, stream);
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.eventEmitter.emit(exports.EVENT_ON_MESSAGE, message);
408
+ this.emit(exports.EVENT_ON_MESSAGE, message);
325
409
  });
326
410
  this.client.setOnHeartbeatTimeout(() => {
327
- this.eventEmitter.emit(exports.EVENT_HEARTBEAT_TIMEOUT);
411
+ this.emit(exports.EVENT_HEARTBEAT_TIMEOUT);
328
412
  });
329
413
  this.client.setOnHeartbeatCallback(() => {
330
- this.eventEmitter.emit(exports.EVENT_HEARTBEAT);
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.eventEmitter.emit(exports.EVENT_ON_CHAT, content);
447
+ this.emit(exports.EVENT_ON_CHAT, content);
364
448
  break;
365
449
  case exports.MESSAGE_TYPE_SIGNAL:
366
450
  {
367
- this.eventEmitter.emit(exports.EVENT_ON_SIGNAL, content);
451
+ this.emit(exports.EVENT_ON_SIGNAL, content);
368
452
  break;
369
453
  }
370
454
  default:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wazo/sdk",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.3",
4
4
  "description": "Wazo's JavaScript Software Development Kit.",
5
5
  "main": "index.js",
6
6
  "types": "lib/types/index.d.ts",