@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
|
@@ -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
|
|
7
|
-
import { WebRtcConfig
|
|
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
|
-
|
|
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
|
|
68
|
-
_getCall
|
|
69
|
-
_onCallEvent
|
|
70
|
-
_onMessage
|
|
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":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"softphone.d.ts","sourceRoot":"","sources":["../../../src/voice/softphone.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAKtC,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAC7C,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAI7C,OAA8B,EAAE,KAAK,iBAAiB,EAAW,KAAK,UAAU,EAAuB,MAAM,0CAA0C,CAAC;AACxJ,OAAO,EAAE,YAAY,EAAW,MAAM,iBAAiB,CAAC;AACxD,OAAO,gBAAgB,MAAM,mBAAmB,CAAC;AAGjD,OAAO,IAAI,MAAM,QAAQ,CAAC;AAG1B,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,eAAe,cAAc,CAAC;AAC3C,eAAO,MAAM,uBAAuB,qBAAqB,CAAC;AAC1D,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAC7C,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AACjD,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AACjD,eAAO,MAAM,wBAAwB,qBAAqB,CAAC;AAC3D,eAAO,MAAM,qBAAqB,mBAAmB,CAAC;AACtD,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AACzD,eAAO,MAAM,qBAAqB,kBAAkB,CAAC;AACrD,eAAO,MAAM,6BAA6B,yBAAyB,CAAC;AACpE,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAC1C,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAC7C,eAAO,MAAM,eAAe,oBAAoB,CAAC;AAEjD,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AACrD,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE;QACN,EAAE,EAAE,MAAM,CAAC;KACZ,CAAC;IACF,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CACpC,CAAC;AAIF,qBAAa,SAAU,SAAQ,YAAY;IACzC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,iBAAiB,EAAE,MAAM,CAAC;IAE1B,eAAe,EAAE,MAAM,CAAC;IAExB,KAAK,EAAE,IAAI,EAAE,CAAC;IAEd,MAAM,EAAE,gBAAgB,CAAC;IAEzB,OAAO,EAAE,OAAO,CAAC;IAEjB,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAExB,cAAc,EAAE,iBAAiB,CAAC;IAElC,OAAO,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAE/B,kBAAkB,EAAE,OAAO,CAAC;IAE5B,cAAc,UAAQ;;IAWtB,OAAO,CAAC,OAAO,GAAE,OAAO,CAAC,YAAY,CAAM;IAyBrC,cAAc;IAId,UAAU;IAYhB,oBAAoB,IAAI,OAAO,GAAG,IAAI;IAIhC,sBAAsB,CAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,MAAM,EACnB,UAAU,GAAE,OAAO,CAAC,YAAY,CAAM,GACrC,OAAO,CAAC,IAAI,CAAC;IAqChB,SAAS;IASH,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IAqCtD,UAAU,CAAC,YAAY,EAAE,IAAI;IAI7B,gBAAgB,CAAC,IAAI,EAAE,IAAI;IAqD3B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAKrC,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAKtC,YAAY,CAAC,UAAU,GAAE,IAAI,GAAG,IAAW;IAI3C,cAAc;IASd,aAAa;IASb,mBAAmB,CAAC,KAAK,EAAE,sBAAsB,GAAG,IAAI;IAIxD,uBAAuB,CAAC,EAAE,EAAE,MAAM;IAOlC,sBAAsB,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;IAOlD,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IAOlF,gBAAgB,CAAC,EAAE,EAAE,MAAM;IAK3B,MAAM,IAAI,OAAO;IAKjB,iBAAiB,CAAC,MAAM,EAAE,MAAM;IAOhC,gBAAgB,CAAC,MAAM,EAAE,MAAM;IAK/B,cAAc,IAAI,IAAI,GAAG,SAAS;IAIlC,cAAc,IAAI,IAAI,EAAE;IAIxB,gBAAgB,IAAI,IAAI,EAAE;IAI1B,IAAI,KAAK,IAAI,UAAU,CAEtB;IAED,IAAI,IAAI,uBAEP;IAED,YAAY;IAIZ,YAAY;IAIZ,WAAW,IAAI,IAAI;IAiJnB,OAAO,CAAC,WAAW;IAInB,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,YAAY;IAapB,OAAO,CAAC,UAAU;CAiCnB;;AAED,wBAA+B"}
|
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
|
+
};
|
package/esm/utils/webrtc.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
251
|
+
return this._hasEstablishedState(EstablishedStates.HELD);
|
|
252
|
+
}
|
|
253
|
+
isEstablishing() {
|
|
254
|
+
return this.sipCall.state === SessionState.Establishing;
|
|
193
255
|
}
|
|
194
256
|
isEstablished() {
|
|
195
|
-
return
|
|
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.
|
|
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(
|
|
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.
|
|
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
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
523
|
+
return this.emit(EVENT_AUDIO_STREAM, stream);
|
|
441
524
|
}
|
|
442
|
-
return this.
|
|
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.
|
|
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.
|
|
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.
|
|
603
|
+
this.emit(EVENT_SHARE_SCREEN_STARTED, screenStream);
|
|
522
604
|
}
|
|
523
605
|
_sendAction(action) {
|
|
524
606
|
this.callActor.send({ type: action });
|