@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
@@ -1,19 +1,23 @@
1
1
  /// <reference types="node" />
2
2
  import { EventEmitter } from 'events';
3
- import type { Message } from 'sip.js/lib/api/message';
4
3
  import type Session from '../domain/Session';
5
4
  import type SipLine from '../domain/SipLine';
6
- import { type SoftphoneActorRef, type StateTypes, ActionTypes } from '../state-machine/softphone-state-machine';
7
- import { WebRtcConfig, SipCall } from '../domain/types';
5
+ import { type SoftphoneActorRef, type StateTypes } from '../state-machine/softphone-state-machine';
6
+ import { WebRtcConfig } from '../domain/types';
8
7
  import WazoWebRTCClient from '../web-rtc-client';
9
8
  import Call from './call';
10
9
  export declare const EVENT_INCOMING = "incoming";
10
+ export declare const EVENT_OUTGOING = "outgoing";
11
11
  export declare const EVENT_HEARTBEAT = "heartbeat";
12
12
  export declare const EVENT_HEARTBEAT_TIMEOUT = "heartbeatTimeout";
13
13
  export declare const EVENT_REGISTERED = "registered";
14
14
  export declare const EVENT_UNREGISTERED = "unregistered";
15
15
  export declare const EVENT_DISCONNECTED = "disconnected";
16
16
  export declare const EVENT_VIDEO_INPUT_CHANGE = "videoInputChange";
17
+ export declare const EVENT_TERMINATE_SOUND = "terminateSound";
18
+ export declare const EVENT_PLAY_HANGUP_SOUND = "playHangupSound";
19
+ export declare const EVENT_PLAY_RING_SOUND = "playRingSound";
20
+ export declare const EVENT_PLAY_INBOUND_CALL_SOUND = "playInboundCallSound";
17
21
  export declare const EVENT_ON_MESSAGE = "message";
18
22
  export declare const EVENT_ON_CHAT = "phone/ON_CHAT";
19
23
  export declare const EVENT_ON_SIGNAL = "phone/ON_SIGNAL";
@@ -26,13 +30,13 @@ export type CallOptions = {
26
30
  withCamera?: boolean;
27
31
  audioOnly?: boolean;
28
32
  conference?: boolean;
33
+ extraHeaders?: Record<string, any>;
29
34
  };
30
35
  export declare class Softphone extends EventEmitter {
31
36
  audioOutputDeviceId?: string;
32
37
  audioRingDeviceId?: string;
33
38
  audioOutputVolume: number;
34
39
  audioRingVolume: number;
35
- eventEmitter: EventEmitter;
36
40
  calls: Call[];
37
41
  client: WazoWebRTCClient;
38
42
  session: Session;
@@ -40,14 +44,19 @@ export declare class Softphone extends EventEmitter {
40
44
  softphoneActor: SoftphoneActorRef;
41
45
  options: Partial<WebRtcConfig>;
42
46
  shouldSendReinvite: boolean;
47
+ ringingEnabled: boolean;
43
48
  constructor();
44
49
  connect(options?: Partial<WebRtcConfig>): Promise<void>;
50
+ hangupAllCalls(): Promise<boolean[]>;
45
51
  disconnect(): Promise<void | null>;
46
52
  getPrimaryWebRtcLine(): SipLine | null;
47
53
  connectWithCredentials(server: string, sipLine: SipLine, displayName: string, rawOptions?: Partial<WebRtcConfig>): Promise<void>;
48
54
  reconnect(): void;
49
55
  call(options: CallOptions): Promise<Call | null>;
50
56
  removeCall(callToRemove: Call): void;
57
+ onCallTerminated(call: Call): void;
58
+ enableRinging(): Promise<void> | void;
59
+ disableRinging(): Promise<void> | void;
51
60
  holdAllCalls(exceptCall?: Call | null): void;
52
61
  startHeartbeat(): void;
53
62
  stopHeartbeat(): void;
@@ -59,18 +68,18 @@ export declare class Softphone extends EventEmitter {
59
68
  hasSfu(): boolean;
60
69
  changeAudioVolume(volume: number): void;
61
70
  changeRingVolume(volume: number): void;
62
- getCalls(): Call[];
63
71
  getCurrentCall(): Call | undefined;
64
- getState(): StateTypes;
72
+ getActiveCalls(): Call[];
73
+ getIncomingCalls(): Call[];
74
+ get state(): StateTypes;
75
+ get user(): string | undefined;
65
76
  getUserAgent(): string;
77
+ isRegistered(): boolean;
66
78
  _bindEvents(): void;
67
- _sendAction(action: ActionTypes): void;
68
- _getCall(sipCall: SipCall): Call | undefined;
69
- _onCallEvent(sipCall: SipCall, eventName: string, ...args: any[]): void;
70
- _onMessage(message: Message & {
71
- method?: string;
72
- body?: string;
73
- }): void;
79
+ private _sendAction;
80
+ private _getCall;
81
+ private _onCallEvent;
82
+ private _onMessage;
74
83
  }
75
84
  declare const _default: Softphone;
76
85
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"softphone.d.ts","sourceRoot":"","sources":["../../../src/voice/softphone.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"softphone.d.ts","sourceRoot":"","sources":["../../../src/voice/softphone.ts"],"names":[],"mappings":"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"}
package/esm/utils/sdp.js CHANGED
@@ -1,3 +1,4 @@
1
+ /* eslint-disable no-underscore-dangle */
1
2
  import sdpParser from 'sdp-transform';
2
3
  import { UserAgent } from 'sip.js/lib/api/user-agent';
3
4
  import { Inviter } from 'sip.js/lib/api';
@@ -128,3 +129,15 @@ export const replaceLocalIpModifier = (description) => Promise.resolve({
128
129
  sdp: description.sdp.replace('c=IN IP4 0.0.0.0', 'c=IN IP4 127.0.0.1'),
129
130
  });
130
131
  export const makeURI = (target, host) => UserAgent.makeURI(`sip:${target}@${host}`);
132
+ export const getCallNumber = (sipCall) => {
133
+ // eslint-disable-next-line
134
+ const identity = sipCall ? sipCall.remoteIdentity || sipCall.assertedIdentity : null;
135
+ // @ts-ignore: private
136
+ return identity ? identity.uri._normal.user : null;
137
+ };
138
+ export const getCallDisplayName = (sipCall) => {
139
+ // eslint-disable-next-line
140
+ const identity = sipCall ? sipCall.remoteIdentity || sipCall.assertedIdentity : null;
141
+ const number = getCallNumber(sipCall);
142
+ return identity ? identity.displayName || number : number;
143
+ };
@@ -173,6 +173,26 @@ export const toggleVideo = (sipCall, muteCamera) => {
173
173
  });
174
174
  }
175
175
  };
176
+ export const isVideoMuted = (sipCall) => {
177
+ const sdh = sipCall.sessionDescriptionHandler;
178
+ const pc = sdh?.peerConnection;
179
+ if (pc?.getSenders) {
180
+ for (const sender of pc.getSenders()) {
181
+ if (sender && sender.track && sender.track.kind === 'video') {
182
+ return !sender.track.enabled;
183
+ }
184
+ }
185
+ }
186
+ else {
187
+ for (const stream of pc.getLocalStreams()) {
188
+ // eslint-disable-next-line no-unreachable-loop
189
+ for (const track of stream.getVideoTracks()) {
190
+ return !track.enabled;
191
+ }
192
+ }
193
+ }
194
+ return true;
195
+ };
176
196
  export const cleanupStream = (stream) => {
177
197
  stream.getTracks().filter(track => track.enabled).forEach(track => track.stop());
178
198
  };
package/esm/voice/call.js CHANGED
@@ -4,13 +4,17 @@ import { createActor } from 'xstate';
4
4
  import { Invitation } from 'sip.js/lib/api/invitation';
5
5
  import { SessionState } from 'sip.js/lib/api/session-state';
6
6
  import callStateMachine, { Actions, EstablishedActions, States, EstablishedStates } from '../state-machine/call-state-machine';
7
+ import newFrom from '../utils/new-from';
8
+ import updateFrom from '../utils/update-from';
7
9
  import IssueReporter from '../service/IssueReporter';
8
- import { getSipSessionId } from '../utils/sdp';
10
+ import softphone, { EVENT_TERMINATE_SOUND } from './softphone';
11
+ import { getCallDisplayName, getCallNumber, getSipSessionId } from '../utils/sdp';
9
12
  import { assertCan, assertState, getState } from '../state-machine/utils';
10
- import { downgradeToAudio, getLocalStream, getLocalVideoStream, getPeerConnection, getRemoteStream, getRemoteVideoStream, getStreamFromConstraints, getWebRtcStats, hasALocalVideoTrack, hasLocalVideo, hasRemoteVideo, isVideoRemotelyHeld, setLocalMediaStream, toggleVideo, upgradeToVideo } from '../utils/webrtc';
13
+ import { downgradeToAudio, getLocalStream, getLocalVideoStream, getPeerConnection, getRemoteStream, getRemoteVideoStream, getStreamFromConstraints, getWebRtcStats, hasALocalVideoTrack, hasLocalVideo, hasRemoteVideo, isVideoMuted, isVideoRemotelyHeld, setLocalMediaStream, toggleVideo, upgradeToVideo } from '../utils/webrtc';
11
14
  const logger = IssueReporter.loggerFor('call');
12
15
  // Events
13
- export const EVENT_CALL_ACCEPTED = 'accepted';
16
+ export const EVENT_CALL_REMOTLY_ACCEPTED = 'remotelyAccepted';
17
+ export const EVENT_CALL_LOCALLY_ACCEPTED = 'locallyAccepted';
14
18
  export const EVENT_CALL_CANCELED = 'canceled';
15
19
  export const EVENT_PROGRESS = 'progress';
16
20
  export const EVENT_EARLY_MEDIA = 'earlyMedia';
@@ -33,12 +37,10 @@ export const EVENT_SHARE_SCREEN_ENDED = 'onScreenShareEnded';
33
37
  export const EVENT_WEB_RTC_STATS = 'webRtcStats';
34
38
  export const EVENT_CAMERA_RESUMED = 'onCameraResumed';
35
39
  export const EVENT_CAMERA_DISABLED = 'onCameraDisabled';
36
- export const EVENT_TERMINATE_SOUND = 'terminateSound';
37
40
  export const MESSAGE_TYPE_CHAT = 'message/TYPE_CHAT';
38
41
  export const MESSAGE_TYPE_SIGNAL = 'message/TYPE_SIGNAL';
39
42
  export const MESSAGE_TRACK_UPDATED = 'onTrackUpdated';
40
43
  class Call extends EventEmitter {
41
- eventEmitter;
42
44
  callActor;
43
45
  // Wazo call id, used in Wazo Websocket and API
44
46
  apiId;
@@ -49,7 +51,55 @@ class Call extends EventEmitter {
49
51
  endTime;
50
52
  phone;
51
53
  shouldSendReinvite = false;
54
+ recording = false;
55
+ displayName;
56
+ number;
52
57
  currentScreenShare;
58
+ static newFrom(call) {
59
+ return newFrom(call, Call);
60
+ }
61
+ static parseCall(apiCall) {
62
+ const sipCall = {
63
+ id: apiCall.sipCallId,
64
+ stateChange: {
65
+ addListener: () => { },
66
+ },
67
+ remoteIdentity: {
68
+ uri: {
69
+ _normal: {
70
+ user: apiCall.calleeNumber,
71
+ },
72
+ },
73
+ },
74
+ // Mock sdk so `hasLocalVideo` will return true.
75
+ sessionDescriptionHandler: {
76
+ localMediaStream: {
77
+ getTracks: () => (apiCall.isVideo ? [{ kind: 'video', readyState: 'live' }] : []),
78
+ },
79
+ },
80
+ };
81
+ const call = new Call(sipCall, softphone);
82
+ // call._sendAction(Actions.ES);
83
+ call.apiId = apiCall.id;
84
+ call.answerTime = new Date(+apiCall.startingTime);
85
+ // Update call state
86
+ const snapshot = call.callActor.getSnapshot();
87
+ if (apiCall.isUp()) {
88
+ snapshot.value = { [States.ESTABLISHED]: EstablishedStates.ONGOING };
89
+ }
90
+ if (apiCall.muted) {
91
+ snapshot.value = { [States.ESTABLISHED]: EstablishedStates.MUTED };
92
+ }
93
+ if (apiCall.isRinging()) {
94
+ snapshot.value = States.RINGING;
95
+ }
96
+ if (apiCall.isRecording()) {
97
+ call.recording = true;
98
+ }
99
+ // @ts-ignore
100
+ call.callActor.update(snapshot);
101
+ return call;
102
+ }
53
103
  constructor(sipCall, phone) {
54
104
  super();
55
105
  this.callActor = createActor(callStateMachine);
@@ -57,6 +107,8 @@ class Call extends EventEmitter {
57
107
  this.creationTime = new Date();
58
108
  this.phone = phone;
59
109
  this.sipCall = sipCall;
110
+ this.number = getCallNumber(sipCall);
111
+ this.displayName = getCallDisplayName(sipCall);
60
112
  this._bindEvents();
61
113
  }
62
114
  async accept(options = {}) {
@@ -68,6 +120,7 @@ class Call extends EventEmitter {
68
120
  this.answerTime = new Date();
69
121
  return this.phone.client.answer(this.sipCall, options.withCamera).then(() => {
70
122
  logger.info('call - accepted', { id: this.id, ...options });
123
+ this.emit(EVENT_CALL_LOCALLY_ACCEPTED, this);
71
124
  return this.id;
72
125
  }).catch(e => {
73
126
  logger.error(e);
@@ -78,7 +131,7 @@ class Call extends EventEmitter {
78
131
  async reject() {
79
132
  assertCan(this.callActor, Actions.REJECT);
80
133
  logger.info('call - reject', { id: this.id });
81
- this.eventEmitter.emit(EVENT_TERMINATE_SOUND, 'call rejected locally');
134
+ this.phone.emit(EVENT_TERMINATE_SOUND, 'call rejected locally');
82
135
  this.shouldSendReinvite = false;
83
136
  return this.hangup();
84
137
  }
@@ -87,7 +140,7 @@ class Call extends EventEmitter {
87
140
  logger.info('call - hangup', { id: this.id });
88
141
  await this.phone.client.hangup(this.sipCall);
89
142
  // Remove this call from softphone's calls
90
- this.phone.removeCall(this);
143
+ this.phone.onCallTerminated(this);
91
144
  return true;
92
145
  }
93
146
  forceCancel() {
@@ -99,7 +152,7 @@ class Call extends EventEmitter {
99
152
  logger.info('call - force cancel, error', e);
100
153
  }
101
154
  }
102
- startConference(withCalls) {
155
+ startConference( /* withCalls: Call[] */) {
103
156
  // @TODO
104
157
  // const participants = [this, ...withCalls].reduce((acc: Record<string, any>, call: Call) => {
105
158
  // acc[call.talkingToIds[0]] = call;
@@ -117,14 +170,14 @@ class Call extends EventEmitter {
117
170
  logger.info('call - mute', { id: this.id });
118
171
  this._sendAction(EstablishedActions.MUTE);
119
172
  this.phone.client.mute(this.sipCall);
120
- this.eventEmitter.emit(EVENT_CALL_MUTED);
173
+ this.emit(EVENT_CALL_MUTED);
121
174
  }
122
175
  unMute() {
123
176
  assertCan(this.callActor, EstablishedActions.UN_MUTE);
124
177
  logger.info('call - un-mute', { id: this.id });
125
178
  this._sendAction(EstablishedActions.UN_MUTE);
126
179
  this.phone.client.unmute(this.sipCall);
127
- this.eventEmitter.emit(EVENT_CALL_UN_MUTED);
180
+ this.emit(EVENT_CALL_UN_MUTED);
128
181
  }
129
182
  async hold() {
130
183
  assertCan(this.callActor, EstablishedActions.HOLD);
@@ -140,7 +193,7 @@ class Call extends EventEmitter {
140
193
  await downgradeToAudio(this.sipCall);
141
194
  }
142
195
  const promise = this.phone.client.hold(this.sipCall, this.isConference(), hasVideo);
143
- this.eventEmitter.emit(EVENT_CALL_HELD);
196
+ this.emit(EVENT_CALL_HELD);
144
197
  return promise;
145
198
  }
146
199
  async resume() {
@@ -167,7 +220,7 @@ class Call extends EventEmitter {
167
220
  this._onScreenSharing(stream, hadVideo, wasDesktop);
168
221
  }
169
222
  };
170
- this.eventEmitter.emit(EVENT_CALL_RESUMED);
223
+ this.emit(EVENT_CALL_RESUMED);
171
224
  return promise.then(() => {
172
225
  const stream = this.getLocalVideoStream();
173
226
  if (wasScreensharing) {
@@ -180,19 +233,37 @@ class Call extends EventEmitter {
180
233
  assertState(this.callActor, States.ESTABLISHED);
181
234
  logger.info('call - turn camera on', { id: this.id });
182
235
  toggleVideo(this.sipCall, true);
183
- this.eventEmitter.emit(EVENT_CAMERA_RESUMED);
236
+ this.emit(EVENT_CAMERA_RESUMED);
184
237
  }
185
238
  turnCameraOff() {
186
239
  assertState(this.callActor, States.ESTABLISHED);
187
240
  logger.info('call - turn camera off', { id: this.id });
188
241
  toggleVideo(this.sipCall, false);
189
- this.eventEmitter.emit(EVENT_CAMERA_DISABLED);
242
+ this.emit(EVENT_CAMERA_DISABLED);
243
+ }
244
+ isMuted() {
245
+ return this._hasEstablishedState(EstablishedStates.MUTED);
246
+ }
247
+ isCameraMuted() {
248
+ return isVideoMuted(this.sipCall);
190
249
  }
191
250
  isHeld() {
192
- return getState(this.callActor) === EstablishedStates.HELD;
251
+ return this._hasEstablishedState(EstablishedStates.HELD);
252
+ }
253
+ isEstablishing() {
254
+ return this.sipCall.state === SessionState.Establishing;
193
255
  }
194
256
  isEstablished() {
195
- return getState(this.callActor) === States.ESTABLISHED;
257
+ return States.ESTABLISHED in this.state;
258
+ }
259
+ isRinging() {
260
+ return this.state === States.RINGING;
261
+ }
262
+ isOutgoing() {
263
+ return this.sipCall instanceof Invitation;
264
+ }
265
+ isIncoming() {
266
+ return !this.isOutgoing();
196
267
  }
197
268
  isConference() {
198
269
  return getPeerConnection(this.sipCall)?.sfu;
@@ -200,7 +271,7 @@ class Call extends EventEmitter {
200
271
  transfer(target) {
201
272
  assertState(this.callActor, States.ESTABLISHED);
202
273
  logger.info('call - transfer', { id: this.id, target });
203
- this.phone.client.transfer(this.sipCall, target);
274
+ return this.phone.client.transfer(this.sipCall, target);
204
275
  }
205
276
  atxfer() {
206
277
  assertState(this.callActor, States.ESTABLISHED);
@@ -320,7 +391,7 @@ class Call extends EventEmitter {
320
391
  catch (e) {
321
392
  console.warn(e);
322
393
  }
323
- this.eventEmitter.emit(EVENT_SHARE_SCREEN_ENDED);
394
+ this.emit(EVENT_SHARE_SCREEN_ENDED);
324
395
  this.currentScreenShare = null;
325
396
  return reinvited;
326
397
  }
@@ -331,7 +402,7 @@ class Call extends EventEmitter {
331
402
  }
332
403
  onAccepted() {
333
404
  this._sendAction(Actions.REMOTLY_ACCEPTED);
334
- this.emit(EVENT_CALL_ACCEPTED);
405
+ this.emit(EVENT_CALL_REMOTLY_ACCEPTED);
335
406
  }
336
407
  onProgress() {
337
408
  this.emit(EVENT_PROGRESS);
@@ -345,10 +416,13 @@ class Call extends EventEmitter {
345
416
  onError(error) {
346
417
  this.emit(EVENT_CALL_ERROR, error);
347
418
  }
419
+ onCallIncoming() {
420
+ this._sendAction(Actions.INCOMING_CALL);
421
+ }
348
422
  onRejected() {
349
423
  this.endTime = new Date();
350
424
  this.emit(EVENT_CALL_REJECTED);
351
- this.phone.removeCall(this);
425
+ this.phone.onCallTerminated(this);
352
426
  }
353
427
  onNetworkStats(stats, previousStats) {
354
428
  this.emit(EVENT_WEB_RTC_STATS, stats, previousStats);
@@ -380,13 +454,21 @@ class Call extends EventEmitter {
380
454
  onReinvite(request, updatedCalleeName, updatedNumber, hadRemoteVideo) {
381
455
  this.emit(EVENT_REINVITE, request, updatedCalleeName, updatedNumber, hadRemoteVideo);
382
456
  }
457
+ updateFrom(call) {
458
+ updateFrom(this, call);
459
+ }
460
+ is(otherCall) {
461
+ return otherCall && this.id && this.id === otherCall.id;
462
+ }
383
463
  get id() {
384
464
  return getSipSessionId(this.sipCall);
385
465
  }
386
- get number() {
387
- const identity = this.sipCall.remoteIdentity || this.sipCall.assertedIdentity;
388
- // @ts-ignore: private
389
- return identity ? identity.uri._normal.user : null;
466
+ get state() {
467
+ return getState(this.callActor);
468
+ }
469
+ _hasEstablishedState(establishedState) {
470
+ const state = this.state;
471
+ return States.ESTABLISHED in state && state[States.ESTABLISHED] === establishedState;
390
472
  }
391
473
  _bindEvents() {
392
474
  if (this.sipCall instanceof Invitation) {
@@ -396,21 +478,22 @@ class Call extends EventEmitter {
396
478
  logger.trace('call - on sip session canceled', { callId: message.callId, id: this.id });
397
479
  onCancel(message);
398
480
  const elsewhere = message.data.indexOf('cause=26') !== -1 && message.data.indexOf('completed elsewhere') !== -1;
399
- this.eventEmitter.emit(EVENT_CALL_CANCELED, elsewhere);
481
+ this.emit(EVENT_CALL_CANCELED, elsewhere);
400
482
  };
401
483
  }
402
484
  this.sipCall.stateChange.addListener((newState) => {
403
485
  switch (newState) {
404
486
  case SessionState.Terminating:
405
487
  logger.info('call - terminating', { id: this.id });
406
- this.eventEmitter.emit(EVENT_CALL_TERMINATING);
488
+ this.emit(EVENT_CALL_TERMINATING);
407
489
  break;
408
490
  case SessionState.Terminated:
409
491
  {
410
492
  logger.info('call - terminated', { id: this.id });
411
493
  // Should be called before `_onCallTerminated` or the callCount will not decrement...
412
494
  this.endTime = new Date();
413
- return this.eventEmitter.emit(EVENT_CALL_ENDED);
495
+ this.emit(EVENT_CALL_ENDED);
496
+ return this.phone.onCallTerminated(this);
414
497
  }
415
498
  default:
416
499
  break;
@@ -437,18 +520,17 @@ class Call extends EventEmitter {
437
520
  kind,
438
521
  });
439
522
  if (kind === 'audio') {
440
- return this.eventEmitter.emit(EVENT_AUDIO_STREAM, stream);
523
+ return this.emit(EVENT_AUDIO_STREAM, stream);
441
524
  }
442
- return this.eventEmitter.emit(EVENT_VIDEO_STREAM, stream, event.track.id, event);
525
+ return this.emit(EVENT_VIDEO_STREAM, stream, event.track.id, event);
443
526
  };
444
527
  peerConnection.onremovestream = (event) => {
445
528
  logger.info('call - on remove stream called on the peer connection', {
446
529
  id: event.stream.id,
447
530
  tracks: event.stream.getTracks(),
448
531
  });
449
- this.eventEmitter.emit(EVENT_REMOVE_STREAM, event.stream);
532
+ this.emit(EVENT_REMOVE_STREAM, event.stream);
450
533
  };
451
- // Client events
452
534
  }
453
535
  _downgradeToAudio(withMessage = true) {
454
536
  assertState(this.callActor, States.ESTABLISHED);
@@ -508,7 +590,7 @@ class Call extends EventEmitter {
508
590
  if (screenTrack) {
509
591
  screenTrack.onended = () => {
510
592
  logger.info('call - on screenshare ended', { hadVideo, id: this.id, screenTrack });
511
- this.eventEmitter.emit(EVENT_SHARE_SCREEN_ENDING);
593
+ this.emit(EVENT_SHARE_SCREEN_ENDING);
512
594
  };
513
595
  }
514
596
  this.currentScreenShare = {
@@ -518,7 +600,7 @@ class Call extends EventEmitter {
518
600
  desktop,
519
601
  };
520
602
  setLocalMediaStream(this.sipCall, screenStream);
521
- this.eventEmitter.emit(EVENT_SHARE_SCREEN_STARTED, screenStream);
603
+ this.emit(EVENT_SHARE_SCREEN_STARTED, screenStream);
522
604
  }
523
605
  _sendAction(action) {
524
606
  this.callActor.send({ type: action });