@wazo/sdk 0.37.30 → 0.37.34

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 (80) hide show
  1. package/dist/wazo-sdk.js +4 -4
  2. package/esm/__tests__/api-client.test.js +30 -0
  3. package/esm/__tests__/web-rtc-client.test.js +57 -6
  4. package/esm/api/calld.js +30 -0
  5. package/esm/api-client.js +30 -0
  6. package/esm/checker/Checker.js +57 -6
  7. package/esm/checker/checks/aor.js +30 -0
  8. package/esm/checker/checks/api.js +30 -0
  9. package/esm/checker/checks/index.js +57 -6
  10. package/esm/checker/checks/webrtc-transport.js +57 -6
  11. package/esm/checker/checks/webrtc.js +57 -6
  12. package/esm/domain/AdHocAPIConference.js +30 -0
  13. package/esm/domain/CallSession.js +9 -1
  14. package/esm/domain/Features.js +30 -0
  15. package/esm/domain/MeetingStatus.js +29 -0
  16. package/esm/domain/Phone/CTIPhone.js +39 -1
  17. package/esm/domain/Phone/WebRTCPhone.js +43 -8
  18. package/esm/domain/__tests__/CallSession.test.js +41 -5
  19. package/esm/domain/__tests__/IndirectTransfer.test.js +9 -1
  20. package/esm/domain/__tests__/Room.test.js +9 -1
  21. package/esm/index.js +100 -14
  22. package/esm/service/CallApi.js +30 -0
  23. package/esm/service/getApiClient.js +30 -0
  24. package/esm/simple/Auth.js +100 -14
  25. package/esm/simple/Configuration.js +100 -14
  26. package/esm/simple/Directory.js +30 -0
  27. package/esm/simple/Phone.js +100 -14
  28. package/esm/simple/index.js +100 -14
  29. package/esm/simple/room/LocalParticipant.js +100 -14
  30. package/esm/simple/room/Participant.js +100 -14
  31. package/esm/simple/room/RemoteParticipant.js +100 -14
  32. package/esm/simple/room/Room.js +100 -14
  33. package/esm/simple/room/SipRoom.js +100 -14
  34. package/esm/simple/room/Stream.js +100 -14
  35. package/esm/simple/room/__tests__/RemoteParticipant.test.js +100 -14
  36. package/esm/simple/utils.js +100 -14
  37. package/esm/web-rtc-client.js +57 -6
  38. package/lib/__tests__/api-client.test.js +30 -0
  39. package/lib/__tests__/web-rtc-client.test.js +57 -6
  40. package/lib/api/calld.js +30 -0
  41. package/lib/api-client.js +30 -0
  42. package/lib/checker/Checker.js +57 -6
  43. package/lib/checker/checks/aor.js +30 -0
  44. package/lib/checker/checks/api.js +30 -0
  45. package/lib/checker/checks/index.js +57 -6
  46. package/lib/checker/checks/webrtc-transport.js +57 -6
  47. package/lib/checker/checks/webrtc.js +57 -6
  48. package/lib/domain/AdHocAPIConference.js +30 -0
  49. package/lib/domain/CallSession.js +9 -1
  50. package/lib/domain/Features.js +30 -0
  51. package/lib/domain/MeetingStatus.js +31 -0
  52. package/lib/domain/Phone/CTIPhone.js +39 -1
  53. package/lib/domain/Phone/WebRTCPhone.js +43 -8
  54. package/lib/domain/__tests__/CallSession.test.js +41 -5
  55. package/lib/domain/__tests__/IndirectTransfer.test.js +9 -1
  56. package/lib/domain/__tests__/Room.test.js +9 -1
  57. package/lib/index.js +100 -14
  58. package/lib/service/CallApi.js +30 -0
  59. package/lib/service/getApiClient.js +30 -0
  60. package/lib/simple/Auth.js +100 -14
  61. package/lib/simple/Configuration.js +100 -14
  62. package/lib/simple/Directory.js +30 -0
  63. package/lib/simple/Phone.js +100 -14
  64. package/lib/simple/index.js +100 -14
  65. package/lib/simple/room/LocalParticipant.js +100 -14
  66. package/lib/simple/room/Participant.js +100 -14
  67. package/lib/simple/room/RemoteParticipant.js +100 -14
  68. package/lib/simple/room/Room.js +100 -14
  69. package/lib/simple/room/SipRoom.js +100 -14
  70. package/lib/simple/room/Stream.js +100 -14
  71. package/lib/simple/room/__tests__/RemoteParticipant.test.js +100 -14
  72. package/lib/simple/utils.js +100 -14
  73. package/lib/web-rtc-client.js +57 -6
  74. package/package.json +1 -1
  75. package/src/api/calld.js +3 -0
  76. package/src/domain/CallSession.js +9 -1
  77. package/src/domain/MeetingStatus.js +27 -0
  78. package/src/domain/Phone/WebRTCPhone.js +34 -7
  79. package/src/domain/__tests__/CallSession.test.js +32 -4
  80. package/src/web-rtc-client.js +27 -6
@@ -4579,6 +4579,34 @@ var chatdMethods = (client , baseUrl ) => ({
4579
4579
  client.get(`${baseUrl}/users/me/rooms/messages`, options),
4580
4580
  });
4581
4581
 
4582
+ //
4583
+
4584
+
4585
+
4586
+
4587
+
4588
+ class MeetingStatus {
4589
+
4590
+
4591
+
4592
+
4593
+ constructor({
4594
+ full,
4595
+ } = {}) {
4596
+ this.full = full;
4597
+
4598
+ // Useful to compare instead of instanceof with minified code
4599
+ this.type = 'MeetingStatus';
4600
+ }
4601
+
4602
+ static parse(plain ) {
4603
+ return new MeetingStatus({
4604
+ full: plain.full,
4605
+ });
4606
+ }
4607
+
4608
+ }
4609
+
4582
4610
  /* */
4583
4611
 
4584
4612
 
@@ -4773,6 +4801,8 @@ var calldMethods = (client , baseUrl ) => ({
4773
4801
  client.put(`${baseUrl}/users/me/calls/${callId}/record/resume`, null, null,
4774
4802
  ApiRequester.successResponseParser),
4775
4803
 
4804
+ guestGetMeetingStatus: (meetingUuid ) =>
4805
+ client.get(`${baseUrl}/guests/me/meetings/${meetingUuid}/status`).then(MeetingStatus.parse),
4776
4806
  });
4777
4807
 
4778
4808
  //
@@ -5332,7 +5362,7 @@ const ON_NETWORK_STATS = 'onNetworkStats';
5332
5362
  const ON_DISCONNECTED = 'onDisconnected';
5333
5363
  class CanceledCallError extends Error {}
5334
5364
 
5335
- const MAX_REGISTER_TRIES = 10;
5365
+ const MAX_REGISTER_TRIES = 5;
5336
5366
 
5337
5367
 
5338
5368
 
@@ -5386,6 +5416,7 @@ class WebRTCClient extends Emitter {
5386
5416
 
5387
5417
 
5388
5418
 
5419
+
5389
5420
 
5390
5421
  // sugar
5391
5422
 
@@ -5448,6 +5479,7 @@ class WebRTCClient extends Emitter {
5448
5479
  this.conferences = {};
5449
5480
  this.networkMonitoringInterval = {};
5450
5481
  this.sessionNetworkStats = {};
5482
+ this.forceClosed = false;
5451
5483
 
5452
5484
  this._boundOnHeartbeat = this._onHeartbeat.bind(this);
5453
5485
  this.heartbeat = new Heartbeat(config.heartbeatDelay, config.heartbeatTimeout, config.maxHeartbeats);
@@ -5569,6 +5601,8 @@ class WebRTCClient extends Emitter {
5569
5601
  skipRegister: this.skipRegister,
5570
5602
  };
5571
5603
 
5604
+ this.forceClosed = false;
5605
+
5572
5606
  if (this.skipRegister) {
5573
5607
  logger.info('sdk webrtc skip register...', logInfo);
5574
5608
  return Promise.resolve();
@@ -5593,7 +5627,15 @@ class WebRTCClient extends Emitter {
5593
5627
  const registerOptions = this._isWeb() ? {} : { extraContactHeaderParams: ['mobility=mobile'] };
5594
5628
 
5595
5629
  const onRegisterFailed = () => {
5596
- logger.info('sdk webrtc registering failed', { tries, registerer: !!this.registerer });
5630
+ logger.info('sdk webrtc registering failed', {
5631
+ tries,
5632
+ registerer: !!this.registerer,
5633
+ forceClosed: this.forceClosed,
5634
+ });
5635
+ if (this.forceClosed) {
5636
+ return;
5637
+ }
5638
+
5597
5639
  this.connectionPromise = null;
5598
5640
  if (this.registerer && this.registerer.waiting) {
5599
5641
  this.registerer.waitingToggle(false);
@@ -5871,8 +5913,9 @@ class WebRTCClient extends Emitter {
5871
5913
  }
5872
5914
  }
5873
5915
 
5874
- async close() {
5875
- logger.info('sdk webrtc closing client', { userAgent: !!this.userAgent });
5916
+ async close(force = false) {
5917
+ logger.info('sdk webrtc closing client', { userAgent: !!this.userAgent, force });
5918
+ this.forceClosed = force;
5876
5919
  this._cleanupMedia();
5877
5920
  this.connectionPromise = null;
5878
5921
 
@@ -5891,7 +5934,7 @@ class WebRTCClient extends Emitter {
5891
5934
  this.userAgent.delegate = null;
5892
5935
  this.userAgent.stateChange.removeAllListeners();
5893
5936
 
5894
- await this._disconnectTransport();
5937
+ await this._disconnectTransport(force);
5895
5938
 
5896
5939
  this._cleanupRegister();
5897
5940
 
@@ -7327,7 +7370,15 @@ class WebRTCClient extends Emitter {
7327
7370
  return UserAgent.makeURI(`sip:${target}@${this.config.host}`);
7328
7371
  }
7329
7372
 
7330
- async _disconnectTransport() {
7373
+ async _disconnectTransport(force = false) {
7374
+ if (force) {
7375
+ // Bypass sip.js state machine that prevent to close WS with the state `Connecting`
7376
+ this.userAgent.transport.disconnectResolve = () => {};
7377
+ // eslint-disable-next-line
7378
+ this.userAgent.transport._ws.close(1000);
7379
+ return;
7380
+ }
7381
+
7331
7382
  // Check if `disconnectPromise` is not present to avoid `Disconnect promise must not be defined` errors.
7332
7383
  if (this.userAgent && this.userAgent.transport && !this.userAgent.transport.disconnectPromise) {
7333
7384
  try {
@@ -4579,6 +4579,34 @@ var chatdMethods = (client , baseUrl ) => ({
4579
4579
  client.get(`${baseUrl}/users/me/rooms/messages`, options),
4580
4580
  });
4581
4581
 
4582
+ //
4583
+
4584
+
4585
+
4586
+
4587
+
4588
+ class MeetingStatus {
4589
+
4590
+
4591
+
4592
+
4593
+ constructor({
4594
+ full,
4595
+ } = {}) {
4596
+ this.full = full;
4597
+
4598
+ // Useful to compare instead of instanceof with minified code
4599
+ this.type = 'MeetingStatus';
4600
+ }
4601
+
4602
+ static parse(plain ) {
4603
+ return new MeetingStatus({
4604
+ full: plain.full,
4605
+ });
4606
+ }
4607
+
4608
+ }
4609
+
4582
4610
  /* */
4583
4611
 
4584
4612
 
@@ -4773,6 +4801,8 @@ var calldMethods = (client , baseUrl ) => ({
4773
4801
  client.put(`${baseUrl}/users/me/calls/${callId}/record/resume`, null, null,
4774
4802
  ApiRequester.successResponseParser),
4775
4803
 
4804
+ guestGetMeetingStatus: (meetingUuid ) =>
4805
+ client.get(`${baseUrl}/guests/me/meetings/${meetingUuid}/status`).then(MeetingStatus.parse),
4776
4806
  });
4777
4807
 
4778
4808
  //
@@ -5332,7 +5362,7 @@ const ON_NETWORK_STATS = 'onNetworkStats';
5332
5362
  const ON_DISCONNECTED = 'onDisconnected';
5333
5363
  class CanceledCallError extends Error {}
5334
5364
 
5335
- const MAX_REGISTER_TRIES = 10;
5365
+ const MAX_REGISTER_TRIES = 5;
5336
5366
 
5337
5367
 
5338
5368
 
@@ -5386,6 +5416,7 @@ class WebRTCClient extends Emitter {
5386
5416
 
5387
5417
 
5388
5418
 
5419
+
5389
5420
 
5390
5421
  // sugar
5391
5422
 
@@ -5448,6 +5479,7 @@ class WebRTCClient extends Emitter {
5448
5479
  this.conferences = {};
5449
5480
  this.networkMonitoringInterval = {};
5450
5481
  this.sessionNetworkStats = {};
5482
+ this.forceClosed = false;
5451
5483
 
5452
5484
  this._boundOnHeartbeat = this._onHeartbeat.bind(this);
5453
5485
  this.heartbeat = new Heartbeat(config.heartbeatDelay, config.heartbeatTimeout, config.maxHeartbeats);
@@ -5569,6 +5601,8 @@ class WebRTCClient extends Emitter {
5569
5601
  skipRegister: this.skipRegister,
5570
5602
  };
5571
5603
 
5604
+ this.forceClosed = false;
5605
+
5572
5606
  if (this.skipRegister) {
5573
5607
  logger.info('sdk webrtc skip register...', logInfo);
5574
5608
  return Promise.resolve();
@@ -5593,7 +5627,15 @@ class WebRTCClient extends Emitter {
5593
5627
  const registerOptions = this._isWeb() ? {} : { extraContactHeaderParams: ['mobility=mobile'] };
5594
5628
 
5595
5629
  const onRegisterFailed = () => {
5596
- logger.info('sdk webrtc registering failed', { tries, registerer: !!this.registerer });
5630
+ logger.info('sdk webrtc registering failed', {
5631
+ tries,
5632
+ registerer: !!this.registerer,
5633
+ forceClosed: this.forceClosed,
5634
+ });
5635
+ if (this.forceClosed) {
5636
+ return;
5637
+ }
5638
+
5597
5639
  this.connectionPromise = null;
5598
5640
  if (this.registerer && this.registerer.waiting) {
5599
5641
  this.registerer.waitingToggle(false);
@@ -5871,8 +5913,9 @@ class WebRTCClient extends Emitter {
5871
5913
  }
5872
5914
  }
5873
5915
 
5874
- async close() {
5875
- logger.info('sdk webrtc closing client', { userAgent: !!this.userAgent });
5916
+ async close(force = false) {
5917
+ logger.info('sdk webrtc closing client', { userAgent: !!this.userAgent, force });
5918
+ this.forceClosed = force;
5876
5919
  this._cleanupMedia();
5877
5920
  this.connectionPromise = null;
5878
5921
 
@@ -5891,7 +5934,7 @@ class WebRTCClient extends Emitter {
5891
5934
  this.userAgent.delegate = null;
5892
5935
  this.userAgent.stateChange.removeAllListeners();
5893
5936
 
5894
- await this._disconnectTransport();
5937
+ await this._disconnectTransport(force);
5895
5938
 
5896
5939
  this._cleanupRegister();
5897
5940
 
@@ -7327,7 +7370,15 @@ class WebRTCClient extends Emitter {
7327
7370
  return UserAgent.makeURI(`sip:${target}@${this.config.host}`);
7328
7371
  }
7329
7372
 
7330
- async _disconnectTransport() {
7373
+ async _disconnectTransport(force = false) {
7374
+ if (force) {
7375
+ // Bypass sip.js state machine that prevent to close WS with the state `Connecting`
7376
+ this.userAgent.transport.disconnectResolve = () => {};
7377
+ // eslint-disable-next-line
7378
+ this.userAgent.transport._ws.close(1000);
7379
+ return;
7380
+ }
7381
+
7331
7382
  // Check if `disconnectPromise` is not present to avoid `Disconnect promise must not be defined` errors.
7332
7383
  if (this.userAgent && this.userAgent.transport && !this.userAgent.transport.disconnectPromise) {
7333
7384
  try {
@@ -4016,6 +4016,34 @@ var chatdMethods = (client , baseUrl ) => ({
4016
4016
  client.get(`${baseUrl}/users/me/rooms/messages`, options),
4017
4017
  });
4018
4018
 
4019
+ //
4020
+
4021
+
4022
+
4023
+
4024
+
4025
+ class MeetingStatus {
4026
+
4027
+
4028
+
4029
+
4030
+ constructor({
4031
+ full,
4032
+ } = {}) {
4033
+ this.full = full;
4034
+
4035
+ // Useful to compare instead of instanceof with minified code
4036
+ this.type = 'MeetingStatus';
4037
+ }
4038
+
4039
+ static parse(plain ) {
4040
+ return new MeetingStatus({
4041
+ full: plain.full,
4042
+ });
4043
+ }
4044
+
4045
+ }
4046
+
4019
4047
  /* */
4020
4048
 
4021
4049
 
@@ -4210,6 +4238,8 @@ var calldMethods = (client , baseUrl ) => ({
4210
4238
  client.put(`${baseUrl}/users/me/calls/${callId}/record/resume`, null, null,
4211
4239
  ApiRequester.successResponseParser),
4212
4240
 
4241
+ guestGetMeetingStatus: (meetingUuid ) =>
4242
+ client.get(`${baseUrl}/guests/me/meetings/${meetingUuid}/status`).then(MeetingStatus.parse),
4213
4243
  });
4214
4244
 
4215
4245
  //
@@ -368,13 +368,21 @@ class CallSession {
368
368
 
369
369
  // Retro-compatibility: `answered` was a boolean before. We can reproduce the behaviour with a getter/setter
370
370
  set answered(value ) {
371
- this.answerTime = value ? new Date() : null;
371
+ this.answerTime = value ? this.answerTime || new Date() : null;
372
372
  }
373
373
 
374
374
  get answered() {
375
375
  return !!this.answerTime;
376
376
  }
377
377
 
378
+ toJSON() {
379
+ const jsonObj = { ...this };
380
+ // $FlowFixMe
381
+ jsonObj.answered = this.answered;
382
+
383
+ return jsonObj;
384
+ }
385
+
378
386
  static parseCall(call ) {
379
387
  return new CallSession({
380
388
  callId: call.id,
@@ -4016,6 +4016,34 @@ var chatdMethods = (client , baseUrl ) => ({
4016
4016
  client.get(`${baseUrl}/users/me/rooms/messages`, options),
4017
4017
  });
4018
4018
 
4019
+ //
4020
+
4021
+
4022
+
4023
+
4024
+
4025
+ class MeetingStatus {
4026
+
4027
+
4028
+
4029
+
4030
+ constructor({
4031
+ full,
4032
+ } = {}) {
4033
+ this.full = full;
4034
+
4035
+ // Useful to compare instead of instanceof with minified code
4036
+ this.type = 'MeetingStatus';
4037
+ }
4038
+
4039
+ static parse(plain ) {
4040
+ return new MeetingStatus({
4041
+ full: plain.full,
4042
+ });
4043
+ }
4044
+
4045
+ }
4046
+
4019
4047
  /* */
4020
4048
 
4021
4049
 
@@ -4210,6 +4238,8 @@ var calldMethods = (client , baseUrl ) => ({
4210
4238
  client.put(`${baseUrl}/users/me/calls/${callId}/record/resume`, null, null,
4211
4239
  ApiRequester.successResponseParser),
4212
4240
 
4241
+ guestGetMeetingStatus: (meetingUuid ) =>
4242
+ client.get(`${baseUrl}/guests/me/meetings/${meetingUuid}/status`).then(MeetingStatus.parse),
4213
4243
  });
4214
4244
 
4215
4245
  //
@@ -0,0 +1,29 @@
1
+ //
2
+
3
+
4
+
5
+
6
+
7
+ class MeetingStatus {
8
+
9
+
10
+
11
+
12
+ constructor({
13
+ full,
14
+ } = {}) {
15
+ this.full = full;
16
+
17
+ // Useful to compare instead of instanceof with minified code
18
+ this.type = 'MeetingStatus';
19
+ }
20
+
21
+ static parse(plain ) {
22
+ return new MeetingStatus({
23
+ full: plain.full,
24
+ });
25
+ }
26
+
27
+ }
28
+
29
+ export default MeetingStatus;
@@ -2042,13 +2042,21 @@ class CallSession {
2042
2042
 
2043
2043
  // Retro-compatibility: `answered` was a boolean before. We can reproduce the behaviour with a getter/setter
2044
2044
  set answered(value ) {
2045
- this.answerTime = value ? new Date() : null;
2045
+ this.answerTime = value ? this.answerTime || new Date() : null;
2046
2046
  }
2047
2047
 
2048
2048
  get answered() {
2049
2049
  return !!this.answerTime;
2050
2050
  }
2051
2051
 
2052
+ toJSON() {
2053
+ const jsonObj = { ...this };
2054
+ // $FlowFixMe
2055
+ jsonObj.answered = this.answered;
2056
+
2057
+ return jsonObj;
2058
+ }
2059
+
2052
2060
  static parseCall(call ) {
2053
2061
  return new CallSession({
2054
2062
  callId: call.id,
@@ -4407,6 +4415,34 @@ class IndirectTransfer {
4407
4415
  }
4408
4416
  }
4409
4417
 
4418
+ //
4419
+
4420
+
4421
+
4422
+
4423
+
4424
+ class MeetingStatus {
4425
+
4426
+
4427
+
4428
+
4429
+ constructor({
4430
+ full,
4431
+ } = {}) {
4432
+ this.full = full;
4433
+
4434
+ // Useful to compare instead of instanceof with minified code
4435
+ this.type = 'MeetingStatus';
4436
+ }
4437
+
4438
+ static parse(plain ) {
4439
+ return new MeetingStatus({
4440
+ full: plain.full,
4441
+ });
4442
+ }
4443
+
4444
+ }
4445
+
4410
4446
  /* */
4411
4447
 
4412
4448
 
@@ -4601,6 +4637,8 @@ var calldMethods = (client , baseUrl ) => ({
4601
4637
  client.put(`${baseUrl}/users/me/calls/${callId}/record/resume`, null, null,
4602
4638
  ApiRequester.successResponseParser),
4603
4639
 
4640
+ guestGetMeetingStatus: (meetingUuid ) =>
4641
+ client.get(`${baseUrl}/guests/me/meetings/${meetingUuid}/status`).then(MeetingStatus.parse),
4604
4642
  });
4605
4643
 
4606
4644
  //
@@ -390,13 +390,21 @@ class CallSession {
390
390
 
391
391
  // Retro-compatibility: `answered` was a boolean before. We can reproduce the behaviour with a getter/setter
392
392
  set answered(value ) {
393
- this.answerTime = value ? new Date() : null;
393
+ this.answerTime = value ? this.answerTime || new Date() : null;
394
394
  }
395
395
 
396
396
  get answered() {
397
397
  return !!this.answerTime;
398
398
  }
399
399
 
400
+ toJSON() {
401
+ const jsonObj = { ...this };
402
+ // $FlowFixMe
403
+ jsonObj.answered = this.answered;
404
+
405
+ return jsonObj;
406
+ }
407
+
400
408
  static parseCall(call ) {
401
409
  return new CallSession({
402
410
  callId: call.id,
@@ -1504,6 +1512,7 @@ class WebRTCPhone extends Emitter {
1504
1512
  }
1505
1513
 
1506
1514
  callSession.answerTime = new Date();
1515
+ this._updateCallSession(callSession);
1507
1516
  this.eventEmitter.emit(ON_CALL_ACCEPTED, callSession, cameraEnabled);
1508
1517
 
1509
1518
  return callSession;
@@ -1713,6 +1722,12 @@ class WebRTCPhone extends Emitter {
1713
1722
  return Promise.resolve(null);
1714
1723
  }
1715
1724
 
1725
+ const sipSessionId = this.getSipSessionIdFromCallSession(callSession);
1726
+ if (sipSessionId && !(sipSessionId in this.callSessions)) {
1727
+ logger.warn('Call session already ended or not found, can\'t accept it.', { sipSessionId });
1728
+ return Promise.resolve(null);
1729
+ }
1730
+
1716
1731
  if (callSession.getId() in this.acceptedSessions) {
1717
1732
  logger.warn('CallSession already accepted.', { id: callSession ? callSession.getId() : 'n/a' });
1718
1733
  return Promise.resolve(callSession.sipCallId);
@@ -1720,6 +1735,7 @@ class WebRTCPhone extends Emitter {
1720
1735
 
1721
1736
  this.shouldSendReinvite = false;
1722
1737
  callSession.answerTime = new Date();
1738
+ this._updateCallSession(callSession);
1723
1739
  this.acceptedSessions[callSession.getId()] = true;
1724
1740
 
1725
1741
  this.eventEmitter.emit(ON_CALL_ANSWERED, callSession);
@@ -1819,7 +1835,7 @@ class WebRTCPhone extends Emitter {
1819
1835
  // Hold current session if different from the current one (we don't want 2 sessions active at the same time).
1820
1836
  if (this.currentSipSession && this.currentSipSession.id !== sipSession.id) {
1821
1837
  logger.info('WebRTC hold call after resume', { id: this.currentSipSession.id });
1822
- this.holdSipSession(this.currentSipSession, callSession);
1838
+ this.holdSipSession(this.currentSipSession, this.currentCallSession);
1823
1839
  }
1824
1840
 
1825
1841
  const promise = this.unholdSipSession(sipSession, callSession, withEvent, isConference);
@@ -2030,6 +2046,7 @@ class WebRTCPhone extends Emitter {
2030
2046
  }
2031
2047
  const callSession = this._createOutgoingCallSession(sipSession, cameraEnabled || false);
2032
2048
  callSession.creationTime = new Date();
2049
+ this._updateCallSession(callSession);
2033
2050
 
2034
2051
  this.eventEmitter.emit(ON_PLAY_PROGRESS_SOUND, this.audioOutputDeviceId, this.audioOutputVolume);
2035
2052
 
@@ -2171,7 +2188,7 @@ class WebRTCPhone extends Emitter {
2171
2188
  return this.client && this.client.isConference(callSession.sipCallId);
2172
2189
  }
2173
2190
 
2174
- async close() {
2191
+ async close(force = false) {
2175
2192
  logger.info('WebRTC close');
2176
2193
 
2177
2194
  try {
@@ -2183,7 +2200,7 @@ class WebRTCPhone extends Emitter {
2183
2200
  logger.error('WebRTC close, unregister error', e);
2184
2201
  }
2185
2202
 
2186
- this.client.close();
2203
+ this.client.close(force);
2187
2204
  this.unbind();
2188
2205
 
2189
2206
  this.incomingSessions = [];
@@ -2309,6 +2326,8 @@ class WebRTCPhone extends Emitter {
2309
2326
  }
2310
2327
 
2311
2328
  callSession.creationTime = new Date();
2329
+ this._updateCallSession(callSession);
2330
+
2312
2331
  this.eventEmitter.emit(ON_CALL_INCOMING, callSession, wantsToDoVideo);
2313
2332
  });
2314
2333
 
@@ -2333,9 +2352,17 @@ class WebRTCPhone extends Emitter {
2333
2352
  });
2334
2353
 
2335
2354
  this.client.on(this.client.ACCEPTED, (sipSession ) => {
2336
- logger.info('WebRTC call accepted', { sipId: sipSession.id });
2355
+ const sessionId = this.getSipSessionId(sipSession);
2356
+ const hasSession = sessionId in this.callSessions;
2337
2357
 
2338
- this._onCallAccepted(sipSession, this.client.hasVideo(this.getSipSessionId(sipSession)));
2358
+ logger.info('WebRTC call accepted', { sipId: sessionId, hasSession });
2359
+
2360
+ if (!hasSession) {
2361
+ logger.warn('Call accepted ignored, session is no longer present in the WebRtcPhone', { sessionId });
2362
+ return;
2363
+ }
2364
+
2365
+ this._onCallAccepted(sipSession, this.client.hasVideo(sessionId));
2339
2366
 
2340
2367
  if (this.audioOutputDeviceId) {
2341
2368
  this.client.changeAudioOutputDevice(this.audioOutputDeviceId);
@@ -2352,6 +2379,8 @@ class WebRTCPhone extends Emitter {
2352
2379
  logger.info('WebRTC call rejected', session.id);
2353
2380
  const callSession = this._createCallSession(session);
2354
2381
  callSession.endTime = new Date();
2382
+ this._updateCallSession(callSession);
2383
+
2355
2384
  this.eventEmitter.emit(ON_CALL_REJECTED, callSession);
2356
2385
  });
2357
2386
 
@@ -2469,9 +2498,14 @@ class WebRTCPhone extends Emitter {
2469
2498
  return this.client.getSipSessionId(sipSession);
2470
2499
  }
2471
2500
 
2472
- _updateCallSession(callSession ) {
2501
+ getSipSessionIdFromCallSession(callSession ) {
2473
2502
  const sipSession = this.findSipSession(callSession);
2474
- const sipSessionId = sipSession ? this.getSipSessionId(sipSession) : null;
2503
+
2504
+ return sipSession ? this.getSipSessionId(sipSession) : null;
2505
+ }
2506
+
2507
+ _updateCallSession(callSession ) {
2508
+ const sipSessionId = this.getSipSessionIdFromCallSession(callSession);
2475
2509
 
2476
2510
  if (sipSessionId) {
2477
2511
  this.callSessions[sipSessionId] = callSession;
@@ -2576,6 +2610,7 @@ class WebRTCPhone extends Emitter {
2576
2610
  displayName: identity ? identity.displayName || number : number,
2577
2611
  startTime: fromSession ? fromSession.startTime : new Date(),
2578
2612
  creationTime: fromSession ? fromSession.creationTime : null,
2613
+ answerTime: fromSession ? fromSession.answerTime : null,
2579
2614
  endTime: fromSession ? fromSession.endTime : null,
2580
2615
  answered: state === SessionState.Established,
2581
2616
  paused: this.client.isCallHeld(sipSession),
@@ -368,13 +368,21 @@ class CallSession {
368
368
 
369
369
  // Retro-compatibility: `answered` was a boolean before. We can reproduce the behaviour with a getter/setter
370
370
  set answered(value ) {
371
- this.answerTime = value ? new Date() : null;
371
+ this.answerTime = value ? this.answerTime || new Date() : null;
372
372
  }
373
373
 
374
374
  get answered() {
375
375
  return !!this.answerTime;
376
376
  }
377
377
 
378
+ toJSON() {
379
+ const jsonObj = { ...this };
380
+ // $FlowFixMe
381
+ jsonObj.answered = this.answered;
382
+
383
+ return jsonObj;
384
+ }
385
+
378
386
  static parseCall(call ) {
379
387
  return new CallSession({
380
388
  callId: call.id,
@@ -400,6 +408,8 @@ class CallSession {
400
408
  }
401
409
  }
402
410
 
411
+ const stringify = cs => JSON.parse(JSON.stringify(cs));
412
+
403
413
  describe('CallSession domain', () => {
404
414
  it('should update from another CallSession without data loss', () => {
405
415
  const callSession = new CallSession({
@@ -438,7 +448,7 @@ describe('CallSession domain', () => {
438
448
  expect(cs2.is(cs1)).toBeTruthy();
439
449
  });
440
450
 
441
- it('should set answerTime when setting answered', () => {
451
+ it('should set answerTime when setting answered to true', () => {
442
452
  const cs1 = new CallSession({});
443
453
  cs1.answered = true;
444
454
 
@@ -447,11 +457,37 @@ describe('CallSession domain', () => {
447
457
 
448
458
  const cs3 = new CallSession({ answered: true });
449
459
 
450
- expect(cs1.answered).toBeTruthy();
460
+ expect(cs1.answered).toEqual(true);
451
461
  expect(cs1.answerTime).not.toBeNull();
452
- expect(cs2.answered).toBeTruthy();
462
+ expect(cs2.answered).toEqual(true);
453
463
  expect(cs2.answerTime).not.toBeNull();
454
- expect(cs3.answered).toBeTruthy();
464
+ expect(cs3.answered).toEqual(true);
455
465
  expect(cs3.answerTime).not.toBeNull();
466
+
467
+ const cs4 = stringify(cs1);
468
+ const cs5 = stringify(cs2);
469
+ const cs6 = stringify(cs3);
470
+
471
+ expect(cs4.answered).toEqual(true);
472
+ expect(cs5.answered).toEqual(true);
473
+ expect(cs6.answered).toEqual(true);
474
+ });
475
+
476
+ it('should reset answerTime when setting answered to false', () => {
477
+ const cs1 = new CallSession({});
478
+ cs1.answered = false;
479
+
480
+ const cs2 = new CallSession({ answered: false });
481
+
482
+ expect(cs1.answered).toEqual(false);
483
+ expect(cs1.answerTime).toBeNull();
484
+ expect(cs2.answered).toEqual(false);
485
+ expect(cs2.answerTime).toBeNull();
486
+
487
+ const cs4 = stringify(cs1);
488
+ const cs5 = stringify(cs2);
489
+
490
+ expect(cs4.answered).toEqual(false);
491
+ expect(cs5.answered).toEqual(false);
456
492
  });
457
493
  });