@wazo/sdk 1.0.0-alpha.0 → 1.0.0-alpha.10
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/README.md +16 -7
- package/dist/wazo-sdk.js +6 -6
- package/esm/__tests__/__voice__/call.test.js +57 -2
- package/esm/__tests__/api-client.test.js +20 -1
- package/esm/api/auth.js +16 -2
- package/esm/checker/checks/webrtc.js +2 -2
- package/esm/domain/CallLog.js +2 -3
- package/esm/domain/Phone/CTIPhone.js +2 -2
- package/esm/domain/Phone/WebRTCPhone.js +64 -63
- package/esm/index.js +5 -1
- package/esm/service/CallApi.js +4 -4
- package/esm/simple/Auth.js +19 -15
- package/esm/simple/Phone.js +6 -5
- package/esm/simple/__tests__/Auth.test.js +8 -64
- package/esm/simple/index.js +2 -1
- package/esm/simple/room/Room.js +2 -2
- package/esm/simple/room/SipRoom.js +2 -2
- package/esm/state-machine/call-state-machine.js +2 -2
- package/esm/state-machine/softphone-state-machine.js +1 -2
- package/esm/state-machine/utils.js +8 -3
- package/esm/types/api/auth.d.ts +3 -0
- package/esm/types/api/auth.d.ts.map +1 -1
- package/esm/types/checker/checks/index.d.ts +1 -1
- package/esm/types/checker/checks/webrtc-transport.d.ts +2 -2
- package/esm/types/checker/checks/webrtc-transport.d.ts.map +1 -1
- package/esm/types/domain/CallLog.d.ts.map +1 -1
- package/esm/types/domain/CallSession.d.ts +3 -3
- package/esm/types/domain/CallSession.d.ts.map +1 -1
- package/esm/types/domain/Phone/CTIPhone.d.ts.map +1 -1
- package/esm/types/domain/Phone/WebRTCPhone.d.ts +22 -22
- package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/esm/types/domain/types.d.ts +0 -4
- package/esm/types/domain/types.d.ts.map +1 -1
- package/esm/types/index.d.ts +5 -1
- package/esm/types/index.d.ts.map +1 -1
- package/esm/types/lib/WazoSessionDescriptionHandler.d.ts +3 -3
- package/esm/types/lib/WazoSessionDescriptionHandler.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/Auth.d.ts +6 -1
- package/esm/types/simple/Auth.d.ts.map +1 -1
- package/esm/types/simple/Phone.d.ts +8 -8
- package/esm/types/simple/Phone.d.ts.map +1 -1
- package/esm/types/simple/index.d.ts +3 -1
- package/esm/types/simple/index.d.ts.map +1 -1
- package/esm/types/state-machine/softphone-state-machine.d.ts +1 -2
- package/esm/types/state-machine/softphone-state-machine.d.ts.map +1 -1
- package/esm/types/state-machine/utils.d.ts +2 -2
- package/esm/types/state-machine/utils.d.ts.map +1 -1
- package/esm/types/utils/api-requester.d.ts.map +1 -1
- package/esm/types/utils/sdp.d.ts +3 -2
- 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 +30 -12
- package/esm/types/voice/call.d.ts.map +1 -1
- package/esm/types/voice/softphone.d.ts +25 -13
- package/esm/types/voice/softphone.d.ts.map +1 -1
- package/esm/types/web-rtc-client.d.ts +39 -38
- package/esm/types/web-rtc-client.d.ts.map +1 -1
- package/esm/utils/__tests__/api-requester.test.js +17 -0
- package/esm/utils/api-requester.js +9 -1
- package/esm/utils/sdp.js +22 -9
- package/esm/utils/webrtc.js +24 -4
- package/esm/voice/call.js +130 -38
- package/esm/voice/softphone.js +145 -35
- package/esm/web-rtc-client.js +61 -58
- package/lib/__tests__/__voice__/call.test.js +56 -1
- package/lib/__tests__/api-client.test.js +20 -1
- package/lib/api/auth.js +16 -2
- package/lib/checker/checks/webrtc.js +1 -1
- package/lib/domain/CallLog.js +2 -3
- package/lib/domain/Phone/CTIPhone.js +2 -2
- package/lib/domain/Phone/WebRTCPhone.js +63 -62
- package/lib/index.js +11 -3
- package/lib/service/CallApi.js +2 -2
- package/lib/simple/Auth.js +21 -15
- package/lib/simple/Phone.js +3 -2
- package/lib/simple/__tests__/Auth.test.js +11 -64
- package/lib/simple/index.js +2 -1
- package/lib/simple/room/Room.js +1 -1
- package/lib/simple/room/SipRoom.js +1 -1
- package/lib/state-machine/call-state-machine.js +2 -2
- package/lib/state-machine/softphone-state-machine.js +1 -2
- package/lib/state-machine/utils.js +8 -3
- package/lib/types/api/auth.d.ts +3 -0
- package/lib/types/api/auth.d.ts.map +1 -1
- package/lib/types/checker/checks/index.d.ts +1 -1
- package/lib/types/checker/checks/webrtc-transport.d.ts +2 -2
- package/lib/types/checker/checks/webrtc-transport.d.ts.map +1 -1
- package/lib/types/domain/CallLog.d.ts.map +1 -1
- package/lib/types/domain/CallSession.d.ts +3 -3
- package/lib/types/domain/CallSession.d.ts.map +1 -1
- package/lib/types/domain/Phone/CTIPhone.d.ts.map +1 -1
- package/lib/types/domain/Phone/WebRTCPhone.d.ts +22 -22
- package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
- package/lib/types/domain/types.d.ts +0 -4
- package/lib/types/domain/types.d.ts.map +1 -1
- package/lib/types/index.d.ts +5 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/lib/WazoSessionDescriptionHandler.d.ts +3 -3
- package/lib/types/lib/WazoSessionDescriptionHandler.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/Auth.d.ts +6 -1
- package/lib/types/simple/Auth.d.ts.map +1 -1
- package/lib/types/simple/Phone.d.ts +8 -8
- package/lib/types/simple/Phone.d.ts.map +1 -1
- package/lib/types/simple/index.d.ts +3 -1
- package/lib/types/simple/index.d.ts.map +1 -1
- package/lib/types/state-machine/softphone-state-machine.d.ts +1 -2
- package/lib/types/state-machine/softphone-state-machine.d.ts.map +1 -1
- package/lib/types/state-machine/utils.d.ts +2 -2
- package/lib/types/state-machine/utils.d.ts.map +1 -1
- package/lib/types/utils/api-requester.d.ts.map +1 -1
- package/lib/types/utils/sdp.d.ts +3 -2
- 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 +30 -12
- package/lib/types/voice/call.d.ts.map +1 -1
- package/lib/types/voice/softphone.d.ts +25 -13
- package/lib/types/voice/softphone.d.ts.map +1 -1
- package/lib/types/web-rtc-client.d.ts +39 -38
- package/lib/types/web-rtc-client.d.ts.map +1 -1
- package/lib/utils/__tests__/api-requester.test.js +17 -0
- package/lib/utils/api-requester.js +9 -1
- package/lib/utils/sdp.js +25 -10
- package/lib/utils/webrtc.js +25 -4
- package/lib/voice/call.js +131 -36
- package/lib/voice/softphone.js +150 -35
- package/lib/web-rtc-client.js +59 -57
- package/package.json +1 -1
- package/src/__tests__/__voice__/call.test.ts +63 -2
- package/src/__tests__/api-client.test.ts +22 -1
- package/src/api/auth.ts +30 -3
- package/src/checker/checks/webrtc-transport.ts +2 -2
- package/src/checker/checks/webrtc.ts +2 -2
- package/src/domain/CallLog.ts +2 -3
- package/src/domain/CallSession.ts +3 -3
- package/src/domain/Phone/CTIPhone.ts +3 -2
- package/src/domain/Phone/WebRTCPhone.ts +95 -93
- package/src/domain/types.ts +0 -5
- package/src/index.ts +6 -1
- package/src/lib/WazoSessionDescriptionHandler.ts +3 -3
- package/src/service/CallApi.ts +7 -7
- package/src/simple/Auth.ts +21 -16
- package/src/simple/Phone.ts +12 -11
- package/src/simple/__tests__/Auth.test.ts +9 -87
- package/src/simple/index.ts +2 -1
- package/src/simple/room/Room.ts +3 -3
- package/src/simple/room/SipRoom.ts +3 -3
- package/src/state-machine/call-state-machine.ts +2 -2
- package/src/state-machine/softphone-state-machine.ts +1 -2
- package/src/state-machine/utils.ts +12 -5
- package/src/utils/__tests__/api-requester.test.ts +20 -0
- package/src/utils/api-requester.ts +9 -1
- package/src/utils/sdp.ts +26 -11
- package/src/utils/webrtc.ts +27 -5
- package/src/voice/call.ts +169 -50
- package/src/voice/softphone.ts +180 -41
- package/src/web-rtc-client.ts +106 -99
|
@@ -5,7 +5,7 @@ import type { MediaStreamFactory } from 'sip.js/lib/platform/web/session-descrip
|
|
|
5
5
|
import type { SessionDescriptionHandlerConfiguration } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-configuration';
|
|
6
6
|
import { SessionDescriptionHandler } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler';
|
|
7
7
|
import { SessionDescriptionHandlerOptions } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-options';
|
|
8
|
-
import { PeerConnection,
|
|
8
|
+
import { PeerConnection, SipCall } from '../domain/types';
|
|
9
9
|
import IssueReporter from '../service/IssueReporter';
|
|
10
10
|
import { type Candidate, addIcesInAllBundles, fixSdp, parseCandidate } from '../utils/sdp';
|
|
11
11
|
|
|
@@ -36,10 +36,10 @@ class WazoSessionDescriptionHandler extends SessionDescriptionHandler {
|
|
|
36
36
|
|
|
37
37
|
isWeb: boolean;
|
|
38
38
|
|
|
39
|
-
session:
|
|
39
|
+
session: SipCall;
|
|
40
40
|
|
|
41
41
|
// eslint-disable-next-line @typescript-eslint/default-param-last
|
|
42
|
-
constructor(logger: Logger, mediaStreamFactory: MediaStreamFactory, sessionDescriptionHandlerConfiguration: SessionDescriptionHandlerConfiguration, isWeb: boolean, session:
|
|
42
|
+
constructor(logger: Logger, mediaStreamFactory: MediaStreamFactory, sessionDescriptionHandlerConfiguration: SessionDescriptionHandlerConfiguration, isWeb: boolean, session: SipCall) {
|
|
43
43
|
super(logger, mediaStreamFactory, sessionDescriptionHandlerConfiguration);
|
|
44
44
|
this.eventEmitter = new EventEmitter();
|
|
45
45
|
this.isWeb = isWeb;
|
package/src/service/CallApi.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import ApiCall from '../domain/Call';
|
|
2
|
+
import Call from '../voice/call';
|
|
2
3
|
import CallLog from '../domain/CallLog';
|
|
3
4
|
import Line from '../domain/Line';
|
|
4
5
|
import Session from '../domain/Session';
|
|
5
6
|
import Relocation from '../domain/Relocation';
|
|
6
|
-
import CallSession from '../domain/CallSession';
|
|
7
7
|
import getApiClient from './getApiClient';
|
|
8
8
|
import Transfer from '../domain/IndirectTransfer';
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ export default class CallApi {
|
|
|
16
16
|
return getApiClient().callLogd.listDistinctCallLogs(offset, limit, distinct);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
static async fetchActiveCalls(): Promise<
|
|
19
|
+
static async fetchActiveCalls(): Promise<ApiCall[]> {
|
|
20
20
|
return getApiClient().calld.listCalls();
|
|
21
21
|
}
|
|
22
22
|
|
|
@@ -37,14 +37,14 @@ export default class CallApi {
|
|
|
37
37
|
return getApiClient().confd.getUserLineSip(session.uuid as string, lineToUse ? <unknown>lineToUse.id as string : '');
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
static async cancelCall(
|
|
41
|
-
return getApiClient().calld.cancelCall(
|
|
40
|
+
static async cancelCall(call: Call): Promise<boolean> {
|
|
41
|
+
return getApiClient().calld.cancelCall(call.apiId || '');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
static async makeCall(callFromLine: Line, extension: string, isMobile = false, callbackAllLines = false): Promise<
|
|
44
|
+
static async makeCall(callFromLine: Line, extension: string, isMobile = false, callbackAllLines = false): Promise<ApiCall | null | undefined> {
|
|
45
45
|
const lineId = callFromLine ? callFromLine.id : null;
|
|
46
46
|
const response = await getApiClient().calld.makeCall(extension, isMobile, lineId, callbackAllLines);
|
|
47
|
-
return
|
|
47
|
+
return ApiCall.parse(response);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
static async relocateCall(callId: string, line: number, contactIdentifier?: string): Promise<Relocation> {
|
package/src/simple/Auth.ts
CHANGED
|
@@ -167,29 +167,26 @@ export class Auth {
|
|
|
167
167
|
return this._onAuthenticated(rawSession as Session);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
async samlLogOut(): Promise<void | { location: string }> {
|
|
171
|
+
return await this.logout(false, true);
|
|
172
|
+
}
|
|
173
|
+
|
|
170
174
|
async initiateIdpAuthentication(domain: string, redirectUrl: string): Promise<{ location: string, saml_session_id: string } | undefined> {
|
|
171
|
-
let response;
|
|
172
175
|
try {
|
|
173
|
-
|
|
176
|
+
return await getApiClient().auth.initiateIdpAuthentication(domain, redirectUrl);
|
|
174
177
|
} catch (e: any) {
|
|
175
|
-
logger.error('Error during IdP authentication
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
+
logger.error('Error during IdP authentication initialization:', e.message);
|
|
178
179
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
throw new NoSamlRouteError('No route found for saml sso');
|
|
180
|
+
if (e.status === 404) {
|
|
181
|
+
throw new NoSamlRouteError('No route found for SAML SSO');
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
if (
|
|
184
|
+
if (e.status === 500) {
|
|
185
185
|
throw new SamlConfigError('SAML/server configuration error');
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
-
|
|
189
|
-
throw new Error(error.message || error.reason);
|
|
188
|
+
throw e;
|
|
190
189
|
}
|
|
191
|
-
|
|
192
|
-
return response.json();
|
|
193
190
|
}
|
|
194
191
|
|
|
195
192
|
async logInViaRefreshToken(refreshToken: string): Promise<Session | null> {
|
|
@@ -225,20 +222,26 @@ export class Auth {
|
|
|
225
222
|
return getApiClient().auth.refreshToken(refreshToken, '', this.expiration);
|
|
226
223
|
}
|
|
227
224
|
|
|
228
|
-
async logout(deleteRefreshToken = true): Promise<void> {
|
|
225
|
+
async logout(deleteRefreshToken = true, saml = false): Promise<void | { location: string }> {
|
|
229
226
|
try {
|
|
230
227
|
Wazo.Websocket.close(true);
|
|
231
228
|
|
|
232
|
-
if (this.clientId && deleteRefreshToken) {
|
|
229
|
+
if (!saml && this.clientId && deleteRefreshToken) {
|
|
233
230
|
await getApiClient().auth.deleteRefreshToken(this.clientId);
|
|
234
231
|
}
|
|
235
232
|
} catch (e: any) {
|
|
236
233
|
// Nothing to
|
|
237
234
|
}
|
|
238
235
|
|
|
236
|
+
let response;
|
|
237
|
+
|
|
239
238
|
try {
|
|
240
239
|
if (this.session?.token) {
|
|
241
|
-
|
|
240
|
+
if (saml) {
|
|
241
|
+
response = await getApiClient().auth.samlLogOut();
|
|
242
|
+
} else {
|
|
243
|
+
await getApiClient().auth.logOut(this.session.token);
|
|
244
|
+
}
|
|
242
245
|
}
|
|
243
246
|
} catch (e: any) {
|
|
244
247
|
// Nothing to
|
|
@@ -250,6 +253,8 @@ export class Auth {
|
|
|
250
253
|
this.authenticated = false;
|
|
251
254
|
this.usingEdgeServer = undefined;
|
|
252
255
|
setFetchOptions({});
|
|
256
|
+
|
|
257
|
+
return response;
|
|
253
258
|
}
|
|
254
259
|
|
|
255
260
|
setOnHostFromHeaders(callback: (hostFromHeaders: string) => void) {
|
package/src/simple/Phone.ts
CHANGED
|
@@ -6,21 +6,22 @@ import type CallSession from '../domain/CallSession';
|
|
|
6
6
|
import AdHocAPIConference from '../domain/AdHocAPIConference';
|
|
7
7
|
import WebRTCPhone, * as PHONE_EVENTS from '../domain/Phone/WebRTCPhone';
|
|
8
8
|
import { MESSAGE_TYPE_CHAT, MESSAGE_TYPE_SIGNAL } from '../domain/Phone/WebRTCPhone';
|
|
9
|
-
import
|
|
9
|
+
import WebRTCClient, { events as clientEvents, transportEvents } from '../web-rtc-client';
|
|
10
10
|
import IssueReporter from '../service/IssueReporter';
|
|
11
11
|
import Emitter from '../utils/Emitter';
|
|
12
12
|
import Wazo from './index';
|
|
13
13
|
import SFUNotAvailableError from '../domain/SFUNotAvailableError';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
14
|
+
import { SipCall, WebRtcConfig } from '../domain/types';
|
|
15
|
+
import { getSipCallId } from '../utils/sdp';
|
|
16
16
|
|
|
17
17
|
const logger = IssueReporter.loggerFor('simple-phone');
|
|
18
18
|
const sipLogger = IssueReporter.loggerFor('sip.js');
|
|
19
19
|
const protocolLogger = IssueReporter.loggerFor('sip');
|
|
20
20
|
const protocolDebugMessages = ['Received WebSocket text message:', 'Sending WebSocket message:'];
|
|
21
21
|
|
|
22
|
+
// @deprecated: Use Wazo.Voice.Softphone instead
|
|
22
23
|
export class Phone extends Emitter {
|
|
23
|
-
client:
|
|
24
|
+
client: WebRTCClient;
|
|
24
25
|
|
|
25
26
|
phone: WebRTCPhone | null | undefined;
|
|
26
27
|
|
|
@@ -187,7 +188,7 @@ export class Phone extends Emitter {
|
|
|
187
188
|
options.log.connector = this._logConnector;
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
this.client = new
|
|
191
|
+
this.client = new WebRTCClient({
|
|
191
192
|
host,
|
|
192
193
|
port: typeof port === 'string' ? parseInt(port, 10) : port,
|
|
193
194
|
displayName,
|
|
@@ -329,14 +330,14 @@ export class Phone extends Emitter {
|
|
|
329
330
|
return this.phone ? this.phone.stopNetworkMonitoring(callSession) : null;
|
|
330
331
|
}
|
|
331
332
|
|
|
332
|
-
|
|
333
|
+
getSipCallId(sipSession: SipCall): string | null | undefined {
|
|
333
334
|
if (!sipSession || !this.phone) {
|
|
334
335
|
return null;
|
|
335
336
|
}
|
|
336
|
-
return
|
|
337
|
+
return getSipCallId(sipSession);
|
|
337
338
|
}
|
|
338
339
|
|
|
339
|
-
sendMessage(body: string, sipSession?:
|
|
340
|
+
sendMessage(body: string, sipSession?: SipCall, contentType = 'text/plain'): void {
|
|
340
341
|
const toSipSession = sipSession || this.getCurrentSipSession();
|
|
341
342
|
|
|
342
343
|
if (!toSipSession || !this.phone) {
|
|
@@ -346,14 +347,14 @@ export class Phone extends Emitter {
|
|
|
346
347
|
this.phone.sendMessage(toSipSession, body, contentType);
|
|
347
348
|
}
|
|
348
349
|
|
|
349
|
-
sendChat(content: string, sipSession?:
|
|
350
|
+
sendChat(content: string, sipSession?: SipCall): void {
|
|
350
351
|
this.sendMessage(JSON.stringify({
|
|
351
352
|
type: MESSAGE_TYPE_CHAT,
|
|
352
353
|
content,
|
|
353
354
|
}), sipSession, 'application/json');
|
|
354
355
|
}
|
|
355
356
|
|
|
356
|
-
sendSignal(content: any, sipSession?:
|
|
357
|
+
sendSignal(content: any, sipSession?: SipCall): void {
|
|
357
358
|
this.sendMessage(JSON.stringify({
|
|
358
359
|
type: MESSAGE_TYPE_SIGNAL,
|
|
359
360
|
content,
|
|
@@ -446,7 +447,7 @@ export class Phone extends Emitter {
|
|
|
446
447
|
return this.phone ? this.phone.hasAVideoTrack(callSession) : false;
|
|
447
448
|
}
|
|
448
449
|
|
|
449
|
-
getCurrentSipSession():
|
|
450
|
+
getCurrentSipSession(): SipCall | null {
|
|
450
451
|
return this.phone?.currentSipSession || null;
|
|
451
452
|
}
|
|
452
453
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import BadResponse from '../../domain/BadResponse';
|
|
2
|
+
import ServerError from '../../domain/ServerError';
|
|
1
3
|
import getApiClient, { setRefreshToken } from '../../service/getApiClient';
|
|
2
4
|
import { Auth as AuthClass } from '../Auth';
|
|
3
5
|
|
|
@@ -124,13 +126,8 @@ describe('simple/Auth', () => {
|
|
|
124
126
|
describe('initiateIdpAuthentication', () => {
|
|
125
127
|
const validResponse = { location: 'idp.com/auth', saml_session_id: 'a1b2C3d4' };
|
|
126
128
|
|
|
127
|
-
it('should return a `location` and `saml_session_id` if the response is
|
|
128
|
-
const initiateIdpAuthentication = jest.fn(() =>
|
|
129
|
-
Promise.resolve({
|
|
130
|
-
ok: true,
|
|
131
|
-
status: 303,
|
|
132
|
-
json: () => Promise.resolve(validResponse),
|
|
133
|
-
}));
|
|
129
|
+
it('should return a `location` and `saml_session_id` if the response is valid', async () => {
|
|
130
|
+
const initiateIdpAuthentication = jest.fn(() => validResponse);
|
|
134
131
|
|
|
135
132
|
(getApiClient as any).mockImplementation(() => ({
|
|
136
133
|
auth: {
|
|
@@ -141,48 +138,9 @@ describe('simple/Auth', () => {
|
|
|
141
138
|
const response = await Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz');
|
|
142
139
|
expect(response).toBe(validResponse);
|
|
143
140
|
});
|
|
144
|
-
it('should return a `NoSamlRouteError` if the response is a 404', async () => {
|
|
145
|
-
const initiateIdpAuthentication = jest.fn(() =>
|
|
146
|
-
Promise.resolve({
|
|
147
|
-
ok: false,
|
|
148
|
-
status: 404,
|
|
149
|
-
}));
|
|
150
|
-
|
|
151
|
-
(getApiClient as any).mockImplementation(() => ({
|
|
152
|
-
auth: {
|
|
153
|
-
initiateIdpAuthentication,
|
|
154
|
-
},
|
|
155
|
-
}));
|
|
156
|
-
|
|
157
|
-
await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow(
|
|
158
|
-
'No route found for saml sso',
|
|
159
|
-
);
|
|
160
|
-
});
|
|
161
141
|
|
|
162
|
-
it('should return
|
|
163
|
-
const initiateIdpAuthentication = jest.fn(() =>
|
|
164
|
-
Promise.resolve({
|
|
165
|
-
ok: false,
|
|
166
|
-
json: () => Promise.resolve({ message: 'an error message', reason: 'an error reason' }),
|
|
167
|
-
}));
|
|
168
|
-
|
|
169
|
-
(getApiClient as any).mockImplementation(() => ({
|
|
170
|
-
auth: {
|
|
171
|
-
initiateIdpAuthentication,
|
|
172
|
-
},
|
|
173
|
-
}));
|
|
174
|
-
|
|
175
|
-
await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow(
|
|
176
|
-
'an error message',
|
|
177
|
-
);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
it('should return an error `reason` if there is no `message` and the response is NOT ok', async () => {
|
|
181
|
-
const initiateIdpAuthentication = jest.fn(() =>
|
|
182
|
-
Promise.resolve({
|
|
183
|
-
ok: false,
|
|
184
|
-
json: () => Promise.resolve({ reason: 'an error reason' }),
|
|
185
|
-
}));
|
|
142
|
+
it('should return a `NoSamlRouteError` if the response is a 404', async () => {
|
|
143
|
+
const initiateIdpAuthentication = jest.fn(() => { throw BadResponse.fromText('an error message', 404); });
|
|
186
144
|
|
|
187
145
|
(getApiClient as any).mockImplementation(() => ({
|
|
188
146
|
auth: {
|
|
@@ -191,48 +149,12 @@ describe('simple/Auth', () => {
|
|
|
191
149
|
}));
|
|
192
150
|
|
|
193
151
|
await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow(
|
|
194
|
-
'
|
|
152
|
+
'No route found for SAML SSO',
|
|
195
153
|
);
|
|
196
154
|
});
|
|
197
155
|
|
|
198
|
-
it('should return
|
|
199
|
-
const initiateIdpAuthentication = jest.fn(() =>
|
|
200
|
-
Promise.resolve({
|
|
201
|
-
ok: false,
|
|
202
|
-
json: () => Promise.resolve({}),
|
|
203
|
-
}));
|
|
204
|
-
|
|
205
|
-
(getApiClient as any).mockImplementation(() => ({
|
|
206
|
-
auth: {
|
|
207
|
-
initiateIdpAuthentication,
|
|
208
|
-
},
|
|
209
|
-
}));
|
|
210
|
-
|
|
211
|
-
await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow('');
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should return a NoSAMLRouteError if the response is a 404', async () => {
|
|
215
|
-
const initiateIdpAuthentication = jest.fn(() =>
|
|
216
|
-
Promise.resolve({
|
|
217
|
-
ok: false,
|
|
218
|
-
status: 404,
|
|
219
|
-
}));
|
|
220
|
-
|
|
221
|
-
(getApiClient as any).mockImplementation(() => ({
|
|
222
|
-
auth: {
|
|
223
|
-
initiateIdpAuthentication,
|
|
224
|
-
},
|
|
225
|
-
}));
|
|
226
|
-
|
|
227
|
-
await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow('No route found for saml sso');
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('should return a SamlConfigError if the response is a 500', async () => {
|
|
231
|
-
const initiateIdpAuthentication = jest.fn(() =>
|
|
232
|
-
Promise.resolve({
|
|
233
|
-
ok: false,
|
|
234
|
-
status: 500,
|
|
235
|
-
}));
|
|
156
|
+
it('should return a `SamlConfigError` if the response is a 500', async () => {
|
|
157
|
+
const initiateIdpAuthentication = jest.fn(() => { throw ServerError.fromText('an error message', 500); });
|
|
236
158
|
|
|
237
159
|
(getApiClient as any).mockImplementation(() => ({
|
|
238
160
|
auth: {
|
package/src/simple/index.ts
CHANGED
|
@@ -41,7 +41,7 @@ import Features from '../domain/Features';
|
|
|
41
41
|
import Meeting from '../domain/Meeting';
|
|
42
42
|
import Checker from '../checker/Checker';
|
|
43
43
|
|
|
44
|
-
import Softphone from '../voice/softphone';
|
|
44
|
+
import Softphone, { Softphone as SoftphoneClass } from '../voice/softphone';
|
|
45
45
|
import Call from '../voice/call';
|
|
46
46
|
|
|
47
47
|
const Wazo = {
|
|
@@ -91,6 +91,7 @@ const Wazo = {
|
|
|
91
91
|
SwitchboardCall,
|
|
92
92
|
WebRTCPhone,
|
|
93
93
|
Meeting,
|
|
94
|
+
SoftphoneClass,
|
|
94
95
|
},
|
|
95
96
|
|
|
96
97
|
// Api
|
package/src/simple/room/Room.ts
CHANGED
|
@@ -9,8 +9,8 @@ import Participant, { RawParticipant } from './Participant';
|
|
|
9
9
|
import RemoteParticipant from './RemoteParticipant';
|
|
10
10
|
import IssueReporter from '../../service/IssueReporter';
|
|
11
11
|
import LocalParticipant from './LocalParticipant';
|
|
12
|
-
import { PeerConnection,
|
|
13
|
-
import {
|
|
12
|
+
import { PeerConnection, SipCall } from '../../domain/types';
|
|
13
|
+
import { getSipCallId } from '../../utils/sdp';
|
|
14
14
|
|
|
15
15
|
export const SIGNAL_TYPE_PARTICIPANT_UPDATE = 'signal/PARTICIPANT_UPDATE';
|
|
16
16
|
export const SIGNAL_TYPE_PARTICIPANT_REQUEST = 'signal/PARTICIPANT_REQUEST';
|
|
@@ -498,7 +498,7 @@ class Room extends Emitter {
|
|
|
498
498
|
this.audioStream = stream;
|
|
499
499
|
|
|
500
500
|
if (!this.roomAudioElement) {
|
|
501
|
-
const sessionId =
|
|
501
|
+
const sessionId = getSipCallId(Wazo.Phone.phone?.currentSipSession as SipCall);
|
|
502
502
|
this.roomAudioElement = await Wazo.Phone.phone?.createAudioElementFor(sessionId as string) as HTMLAudioElement;
|
|
503
503
|
this.roomAudioElement.srcObject = stream;
|
|
504
504
|
} else {
|
|
@@ -3,8 +3,8 @@ import CallSession from '../../domain/CallSession';
|
|
|
3
3
|
import IssueReporter from '../../service/IssueReporter';
|
|
4
4
|
import Wazo from '../index';
|
|
5
5
|
import Room from './Room';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { SipCall } from '../../domain/types';
|
|
7
|
+
import { getSipCallId } from '../../utils/sdp';
|
|
8
8
|
|
|
9
9
|
const logger = IssueReporter.loggerFor('sdk-sip-room');
|
|
10
10
|
|
|
@@ -157,7 +157,7 @@ class SipRoom extends Room {
|
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
_getCurrentSipCallIs() {
|
|
160
|
-
return
|
|
160
|
+
return getSipCallId(Wazo.Phone.phone?.currentSipSession as SipCall);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
}
|
|
@@ -43,7 +43,7 @@ export type EstablishedActionTypes = typeof EstablishedActions[keyof typeof Esta
|
|
|
43
43
|
|
|
44
44
|
const callMachine = setup({
|
|
45
45
|
guards: {
|
|
46
|
-
isRegistered: () => softphone.
|
|
46
|
+
isRegistered: () => softphone.state === SoftphoneStates.REGISTERED,
|
|
47
47
|
},
|
|
48
48
|
}).createMachine({
|
|
49
49
|
id: 'call',
|
|
@@ -53,7 +53,7 @@ const callMachine = setup({
|
|
|
53
53
|
[States.IDLE]: {
|
|
54
54
|
on: {
|
|
55
55
|
[Actions.INCOMING_CALL]: {
|
|
56
|
-
|
|
56
|
+
guard: 'isRegistered',
|
|
57
57
|
target: States.RINGING,
|
|
58
58
|
},
|
|
59
59
|
[Actions.MAKE_CALL]: {
|
|
@@ -12,7 +12,6 @@ export const Actions = {
|
|
|
12
12
|
REGISTER: 'register',
|
|
13
13
|
REGISTER_DONE: 'registerDone',
|
|
14
14
|
UNREGISTER: 'unregister',
|
|
15
|
-
UNREGISTERED: 'unregistered',
|
|
16
15
|
TRANSPORT_CLOSED: 'transportClosed',
|
|
17
16
|
} as const;
|
|
18
17
|
|
|
@@ -32,7 +31,7 @@ const softphoneMachine = setup({}).createMachine({
|
|
|
32
31
|
on: {
|
|
33
32
|
[Actions.REGISTER_DONE]: States.REGISTERED,
|
|
34
33
|
[Actions.TRANSPORT_CLOSED]: States.UNREGISTERED,
|
|
35
|
-
[Actions.
|
|
34
|
+
[Actions.UNREGISTER]: States.UNREGISTERED,
|
|
36
35
|
},
|
|
37
36
|
},
|
|
38
37
|
[States.REGISTERED]: {
|
|
@@ -8,16 +8,23 @@ import IssueReporter from '../service/IssueReporter';
|
|
|
8
8
|
|
|
9
9
|
const logger = IssueReporter.loggerFor('state-machine');
|
|
10
10
|
|
|
11
|
-
export const getState = (actor: SoftphoneActorRef | CallActorRef): SoftphoneStates | CallStates | EstablishedStateTypes => {
|
|
12
|
-
return actor.getSnapshot().value as SoftphoneStates | CallStates | EstablishedStateTypes
|
|
11
|
+
export const getState = (actor: SoftphoneActorRef | CallActorRef): SoftphoneStates | CallStates | EstablishedStateTypes | Record<CallStates, EstablishedStateTypes> => {
|
|
12
|
+
return actor.getSnapshot().value as SoftphoneStates | CallStates | EstablishedStateTypes | Record<CallStates, EstablishedStateTypes>;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const can = (actor: SoftphoneActorRef | CallActorRef, action: SoftphoneActions | CallActions | EstablishedCallActions): boolean => {
|
|
16
16
|
return actor.getSnapshot().can({ type: action });
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
export const hasState = (actor: SoftphoneActorRef | CallActorRef, state: SoftphoneStates | CallStates): boolean => {
|
|
20
|
-
|
|
19
|
+
export const hasState = (actor: SoftphoneActorRef | CallActorRef, state: SoftphoneStates | CallStates, innerState?: EstablishedStateTypes): boolean => {
|
|
20
|
+
const currentState = getState(actor);
|
|
21
|
+
|
|
22
|
+
// When using nested states, we'll have an object here
|
|
23
|
+
if (typeof currentState === 'object') {
|
|
24
|
+
return innerState ? state in currentState && currentState[state as CallStates] === innerState : state in currentState;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return currentState === state;
|
|
21
28
|
};
|
|
22
29
|
|
|
23
30
|
export const waitUntilState = (actor: SoftphoneActorRef | CallActorRef, state: SoftphoneStates | CallStates, timeout = 5000): Promise<unknown> => {
|
|
@@ -45,7 +52,7 @@ export const assertCan = (actor: SoftphoneActorRef | CallActorRef, action: Softp
|
|
|
45
52
|
|
|
46
53
|
export const assertState = (actor: SoftphoneActorRef | CallActorRef, state: SoftphoneStates | CallStates): void => {
|
|
47
54
|
if (!hasState(actor, state)) {
|
|
48
|
-
const message = `Invalid state ${state}`;
|
|
55
|
+
const message = `Invalid state ${state}, has state ${getState(actor)}`;
|
|
49
56
|
logger.warn(message);
|
|
50
57
|
|
|
51
58
|
throw new InvalidState(message, state);
|
|
@@ -158,3 +158,23 @@ describe('With a refresh token', () => {
|
|
|
158
158
|
});
|
|
159
159
|
});
|
|
160
160
|
});
|
|
161
|
+
|
|
162
|
+
describe('base64Encode', () => {
|
|
163
|
+
it('should encode a string using base64', () => {
|
|
164
|
+
const input = 'Hello World!';
|
|
165
|
+
const expectedOutput = 'SGVsbG8gV29ybGQh';
|
|
166
|
+
expect(ApiRequester.base64Encode(input)).toBe(expectedOutput);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should handle special characters', () => {
|
|
170
|
+
const input = 'éà$@€';
|
|
171
|
+
const expectedOutput = 'w6nDoCRA4oKs';
|
|
172
|
+
expect(ApiRequester.base64Encode(input)).toBe(expectedOutput);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should handle empty string', () => {
|
|
176
|
+
const input = '';
|
|
177
|
+
const expectedOutput = '';
|
|
178
|
+
expect(ApiRequester.base64Encode(input)).toBe(expectedOutput);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
@@ -64,7 +64,15 @@ export default class ApiRequester {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
static base64Encode(str: string): string {
|
|
67
|
-
|
|
67
|
+
if (typeof btoa !== 'undefined') {
|
|
68
|
+
try {
|
|
69
|
+
return btoa(str);
|
|
70
|
+
} catch (error) {
|
|
71
|
+
// fall back to Base64.encode if btoa fails
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return Base64.encode(str);
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
// @see https://github.com/facebook/flow/issues/183#issuecomment-358607052
|
package/src/utils/sdp.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
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
|
-
import { Inviter } from 'sip.js/lib/api';
|
|
4
|
+
import { Inviter, Invitation } from 'sip.js/lib/api';
|
|
4
5
|
import { URI } from 'sip.js/lib/grammar/uri';
|
|
5
6
|
|
|
6
7
|
import type { SipCall } from '../domain/types';
|
|
7
8
|
|
|
8
|
-
export const SIP_ID_LENGTH = 36;
|
|
9
|
-
|
|
10
9
|
export type Candidate = {
|
|
11
10
|
foundation: string;
|
|
12
11
|
component: number;
|
|
@@ -146,25 +145,26 @@ export const addIcesInAllBundles = (sdp: string) => {
|
|
|
146
145
|
return sdpParser.write(parsedSdp);
|
|
147
146
|
};
|
|
148
147
|
|
|
149
|
-
export const
|
|
148
|
+
export const getSipCallId = (sipSession: SipCall | null | undefined): string => {
|
|
150
149
|
if (!sipSession) {
|
|
151
150
|
return '';
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
const message = sipSession.message as Partial<{ callId?: string }>;
|
|
155
|
-
if (message?.callId) {
|
|
156
|
-
return message.callId.substring(0, SIP_ID_LENGTH);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
153
|
// For Inviter
|
|
160
154
|
// @ts-ignore: private
|
|
161
155
|
if (sipSession instanceof Inviter && sipSession.outgoingRequestMessage) {
|
|
162
156
|
// @ts-ignore: private
|
|
163
|
-
return sipSession.outgoingRequestMessage.callId
|
|
157
|
+
return sipSession.outgoingRequestMessage.callId;
|
|
164
158
|
}
|
|
165
159
|
|
|
166
160
|
// For Invitation
|
|
167
|
-
|
|
161
|
+
// @ts-ignore: private
|
|
162
|
+
if (sipSession instanceof Invitation && sipSession.incomingInviteRequest.message) {
|
|
163
|
+
// @ts-ignore: private
|
|
164
|
+
return sipSession.incomingInviteRequest.message.callId;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return sipSession.id;
|
|
168
168
|
};
|
|
169
169
|
|
|
170
170
|
// We need to replace 0.0.0.0 to 127.0.0.1 in the sdp to avoid MOH during a createOffer.
|
|
@@ -174,3 +174,18 @@ export const replaceLocalIpModifier = (description: Record<string, any>) => Prom
|
|
|
174
174
|
});
|
|
175
175
|
|
|
176
176
|
export const makeURI = (target: string, host: string): URI | undefined => UserAgent.makeURI(`sip:${target}@${host}`);
|
|
177
|
+
|
|
178
|
+
export const getCallNumber = (sipCall: SipCall): string => {
|
|
179
|
+
// eslint-disable-next-line
|
|
180
|
+
const identity = sipCall ? sipCall.remoteIdentity || sipCall.assertedIdentity : null;
|
|
181
|
+
// @ts-ignore: private
|
|
182
|
+
return identity ? identity.uri._normal.user : null;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export const getCallDisplayName = (sipCall: SipCall): string => {
|
|
186
|
+
// eslint-disable-next-line
|
|
187
|
+
const identity = sipCall ? sipCall.remoteIdentity || sipCall.assertedIdentity : null;
|
|
188
|
+
const number = getCallNumber(sipCall);
|
|
189
|
+
|
|
190
|
+
return identity ? identity.displayName || number : number;
|
|
191
|
+
};
|
package/src/utils/webrtc.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint-disable no-underscore-dangle, no-param-reassign */
|
|
1
|
+
/* eslint-disable no-underscore-dangle, no-param-reassign, no-restricted-syntax */
|
|
2
2
|
import type { SessionDescriptionHandler } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler';
|
|
3
3
|
import type { SessionDescriptionHandlerFactoryOptions } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-factory-options';
|
|
4
4
|
import type { SessionDescriptionHandlerConfiguration } from 'sip.js/lib/platform/web/session-description-handler/session-description-handler-configuration';
|
|
@@ -7,7 +7,7 @@ import { stripVideo } from 'sip.js/lib/platform/web';
|
|
|
7
7
|
|
|
8
8
|
import WazoSessionDescriptionHandler, { wazoMediaStreamFactory } from '../lib/WazoSessionDescriptionHandler';
|
|
9
9
|
import { SipCall, PeerConnection, UserAgentOptions, WebRtcConfig, UserAgentConfigOverrides } from '../domain/types';
|
|
10
|
-
import { getVideoDirection, hasAnActiveVideo, makeURI, replaceLocalIpModifier } from './sdp';
|
|
10
|
+
import { getSipCallId, getVideoDirection, hasAnActiveVideo, makeURI, replaceLocalIpModifier } from './sdp';
|
|
11
11
|
import IssueReporter from '../service/IssueReporter';
|
|
12
12
|
import { lastIndexOf } from './array';
|
|
13
13
|
|
|
@@ -246,13 +246,35 @@ export const toggleVideo = (sipCall: SipCall, muteCamera: boolean): void => {
|
|
|
246
246
|
}
|
|
247
247
|
};
|
|
248
248
|
|
|
249
|
+
export const isVideoMuted = (sipCall: SipCall): boolean => {
|
|
250
|
+
const sdh = sipCall.sessionDescriptionHandler as SessionDescriptionHandler;
|
|
251
|
+
const pc = sdh?.peerConnection as PeerConnection;
|
|
252
|
+
|
|
253
|
+
if (pc?.getSenders) {
|
|
254
|
+
for (const sender of pc.getSenders()) {
|
|
255
|
+
if (sender && sender.track && sender.track.kind === 'video') {
|
|
256
|
+
return !sender.track.enabled;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
for (const stream of pc.getLocalStreams()) {
|
|
261
|
+
// eslint-disable-next-line no-unreachable-loop
|
|
262
|
+
for (const track of stream.getVideoTracks()) {
|
|
263
|
+
return !track.enabled;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return true;
|
|
269
|
+
};
|
|
270
|
+
|
|
249
271
|
export const cleanupStream = (stream: MediaStream): void => {
|
|
250
272
|
stream.getTracks().filter(track => track.enabled).forEach(track => track.stop());
|
|
251
273
|
};
|
|
252
274
|
|
|
253
275
|
export const setLocalMediaStream = (sipCall: SipCall, newStream: MediaStream): void => {
|
|
254
276
|
logger.info('setting local media stream', {
|
|
255
|
-
id: sipCall
|
|
277
|
+
id: getSipCallId(sipCall),
|
|
256
278
|
tracks: newStream ? newStream.getTracks() : null,
|
|
257
279
|
});
|
|
258
280
|
|
|
@@ -268,7 +290,7 @@ export const updateRemoteStream = (sipCall: SipCall, allTracks = true): void =>
|
|
|
268
290
|
const pc = getPeerConnection(sipCall);
|
|
269
291
|
|
|
270
292
|
logger.info('Updating remote stream', {
|
|
271
|
-
id: sipCall
|
|
293
|
+
id: getSipCallId(sipCall),
|
|
272
294
|
tracks: remoteStream ? remoteStream.getTracks() : null,
|
|
273
295
|
receiverTracks: pc && pc.getReceivers ? pc.getReceivers().map((receiver) => receiver.track) : null,
|
|
274
296
|
});
|
|
@@ -329,7 +351,7 @@ export const getMediaConfiguration = (enableVideo: boolean, conference = false,
|
|
|
329
351
|
return {
|
|
330
352
|
constraints: {
|
|
331
353
|
// Exact constraint are not supported with `getDisplayMedia` and we must have a video=false in desktop screenshare
|
|
332
|
-
audio: screenSharing ? !isDesktop :
|
|
354
|
+
audio: screenSharing ? !isDesktop : withAudio ? getAudioConstraints(true) : false,
|
|
333
355
|
video: screenSharing ? isDesktop ? {
|
|
334
356
|
mandatory: {
|
|
335
357
|
chromeMediaSource: 'desktop',
|