@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.
Files changed (162) hide show
  1. package/README.md +16 -7
  2. package/dist/wazo-sdk.js +6 -6
  3. package/esm/__tests__/__voice__/call.test.js +57 -2
  4. package/esm/__tests__/api-client.test.js +20 -1
  5. package/esm/api/auth.js +16 -2
  6. package/esm/checker/checks/webrtc.js +2 -2
  7. package/esm/domain/CallLog.js +2 -3
  8. package/esm/domain/Phone/CTIPhone.js +2 -2
  9. package/esm/domain/Phone/WebRTCPhone.js +64 -63
  10. package/esm/index.js +5 -1
  11. package/esm/service/CallApi.js +4 -4
  12. package/esm/simple/Auth.js +19 -15
  13. package/esm/simple/Phone.js +6 -5
  14. package/esm/simple/__tests__/Auth.test.js +8 -64
  15. package/esm/simple/index.js +2 -1
  16. package/esm/simple/room/Room.js +2 -2
  17. package/esm/simple/room/SipRoom.js +2 -2
  18. package/esm/state-machine/call-state-machine.js +2 -2
  19. package/esm/state-machine/softphone-state-machine.js +1 -2
  20. package/esm/state-machine/utils.js +8 -3
  21. package/esm/types/api/auth.d.ts +3 -0
  22. package/esm/types/api/auth.d.ts.map +1 -1
  23. package/esm/types/checker/checks/index.d.ts +1 -1
  24. package/esm/types/checker/checks/webrtc-transport.d.ts +2 -2
  25. package/esm/types/checker/checks/webrtc-transport.d.ts.map +1 -1
  26. package/esm/types/domain/CallLog.d.ts.map +1 -1
  27. package/esm/types/domain/CallSession.d.ts +3 -3
  28. package/esm/types/domain/CallSession.d.ts.map +1 -1
  29. package/esm/types/domain/Phone/CTIPhone.d.ts.map +1 -1
  30. package/esm/types/domain/Phone/WebRTCPhone.d.ts +22 -22
  31. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  32. package/esm/types/domain/types.d.ts +0 -4
  33. package/esm/types/domain/types.d.ts.map +1 -1
  34. package/esm/types/index.d.ts +5 -1
  35. package/esm/types/index.d.ts.map +1 -1
  36. package/esm/types/lib/WazoSessionDescriptionHandler.d.ts +3 -3
  37. package/esm/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
  38. package/esm/types/service/CallApi.d.ts +5 -5
  39. package/esm/types/service/CallApi.d.ts.map +1 -1
  40. package/esm/types/simple/Auth.d.ts +6 -1
  41. package/esm/types/simple/Auth.d.ts.map +1 -1
  42. package/esm/types/simple/Phone.d.ts +8 -8
  43. package/esm/types/simple/Phone.d.ts.map +1 -1
  44. package/esm/types/simple/index.d.ts +3 -1
  45. package/esm/types/simple/index.d.ts.map +1 -1
  46. package/esm/types/state-machine/softphone-state-machine.d.ts +1 -2
  47. package/esm/types/state-machine/softphone-state-machine.d.ts.map +1 -1
  48. package/esm/types/state-machine/utils.d.ts +2 -2
  49. package/esm/types/state-machine/utils.d.ts.map +1 -1
  50. package/esm/types/utils/api-requester.d.ts.map +1 -1
  51. package/esm/types/utils/sdp.d.ts +3 -2
  52. package/esm/types/utils/sdp.d.ts.map +1 -1
  53. package/esm/types/utils/webrtc.d.ts +1 -0
  54. package/esm/types/utils/webrtc.d.ts.map +1 -1
  55. package/esm/types/voice/call.d.ts +30 -12
  56. package/esm/types/voice/call.d.ts.map +1 -1
  57. package/esm/types/voice/softphone.d.ts +25 -13
  58. package/esm/types/voice/softphone.d.ts.map +1 -1
  59. package/esm/types/web-rtc-client.d.ts +39 -38
  60. package/esm/types/web-rtc-client.d.ts.map +1 -1
  61. package/esm/utils/__tests__/api-requester.test.js +17 -0
  62. package/esm/utils/api-requester.js +9 -1
  63. package/esm/utils/sdp.js +22 -9
  64. package/esm/utils/webrtc.js +24 -4
  65. package/esm/voice/call.js +130 -38
  66. package/esm/voice/softphone.js +145 -35
  67. package/esm/web-rtc-client.js +61 -58
  68. package/lib/__tests__/__voice__/call.test.js +56 -1
  69. package/lib/__tests__/api-client.test.js +20 -1
  70. package/lib/api/auth.js +16 -2
  71. package/lib/checker/checks/webrtc.js +1 -1
  72. package/lib/domain/CallLog.js +2 -3
  73. package/lib/domain/Phone/CTIPhone.js +2 -2
  74. package/lib/domain/Phone/WebRTCPhone.js +63 -62
  75. package/lib/index.js +11 -3
  76. package/lib/service/CallApi.js +2 -2
  77. package/lib/simple/Auth.js +21 -15
  78. package/lib/simple/Phone.js +3 -2
  79. package/lib/simple/__tests__/Auth.test.js +11 -64
  80. package/lib/simple/index.js +2 -1
  81. package/lib/simple/room/Room.js +1 -1
  82. package/lib/simple/room/SipRoom.js +1 -1
  83. package/lib/state-machine/call-state-machine.js +2 -2
  84. package/lib/state-machine/softphone-state-machine.js +1 -2
  85. package/lib/state-machine/utils.js +8 -3
  86. package/lib/types/api/auth.d.ts +3 -0
  87. package/lib/types/api/auth.d.ts.map +1 -1
  88. package/lib/types/checker/checks/index.d.ts +1 -1
  89. package/lib/types/checker/checks/webrtc-transport.d.ts +2 -2
  90. package/lib/types/checker/checks/webrtc-transport.d.ts.map +1 -1
  91. package/lib/types/domain/CallLog.d.ts.map +1 -1
  92. package/lib/types/domain/CallSession.d.ts +3 -3
  93. package/lib/types/domain/CallSession.d.ts.map +1 -1
  94. package/lib/types/domain/Phone/CTIPhone.d.ts.map +1 -1
  95. package/lib/types/domain/Phone/WebRTCPhone.d.ts +22 -22
  96. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  97. package/lib/types/domain/types.d.ts +0 -4
  98. package/lib/types/domain/types.d.ts.map +1 -1
  99. package/lib/types/index.d.ts +5 -1
  100. package/lib/types/index.d.ts.map +1 -1
  101. package/lib/types/lib/WazoSessionDescriptionHandler.d.ts +3 -3
  102. package/lib/types/lib/WazoSessionDescriptionHandler.d.ts.map +1 -1
  103. package/lib/types/service/CallApi.d.ts +5 -5
  104. package/lib/types/service/CallApi.d.ts.map +1 -1
  105. package/lib/types/simple/Auth.d.ts +6 -1
  106. package/lib/types/simple/Auth.d.ts.map +1 -1
  107. package/lib/types/simple/Phone.d.ts +8 -8
  108. package/lib/types/simple/Phone.d.ts.map +1 -1
  109. package/lib/types/simple/index.d.ts +3 -1
  110. package/lib/types/simple/index.d.ts.map +1 -1
  111. package/lib/types/state-machine/softphone-state-machine.d.ts +1 -2
  112. package/lib/types/state-machine/softphone-state-machine.d.ts.map +1 -1
  113. package/lib/types/state-machine/utils.d.ts +2 -2
  114. package/lib/types/state-machine/utils.d.ts.map +1 -1
  115. package/lib/types/utils/api-requester.d.ts.map +1 -1
  116. package/lib/types/utils/sdp.d.ts +3 -2
  117. package/lib/types/utils/sdp.d.ts.map +1 -1
  118. package/lib/types/utils/webrtc.d.ts +1 -0
  119. package/lib/types/utils/webrtc.d.ts.map +1 -1
  120. package/lib/types/voice/call.d.ts +30 -12
  121. package/lib/types/voice/call.d.ts.map +1 -1
  122. package/lib/types/voice/softphone.d.ts +25 -13
  123. package/lib/types/voice/softphone.d.ts.map +1 -1
  124. package/lib/types/web-rtc-client.d.ts +39 -38
  125. package/lib/types/web-rtc-client.d.ts.map +1 -1
  126. package/lib/utils/__tests__/api-requester.test.js +17 -0
  127. package/lib/utils/api-requester.js +9 -1
  128. package/lib/utils/sdp.js +25 -10
  129. package/lib/utils/webrtc.js +25 -4
  130. package/lib/voice/call.js +131 -36
  131. package/lib/voice/softphone.js +150 -35
  132. package/lib/web-rtc-client.js +59 -57
  133. package/package.json +1 -1
  134. package/src/__tests__/__voice__/call.test.ts +63 -2
  135. package/src/__tests__/api-client.test.ts +22 -1
  136. package/src/api/auth.ts +30 -3
  137. package/src/checker/checks/webrtc-transport.ts +2 -2
  138. package/src/checker/checks/webrtc.ts +2 -2
  139. package/src/domain/CallLog.ts +2 -3
  140. package/src/domain/CallSession.ts +3 -3
  141. package/src/domain/Phone/CTIPhone.ts +3 -2
  142. package/src/domain/Phone/WebRTCPhone.ts +95 -93
  143. package/src/domain/types.ts +0 -5
  144. package/src/index.ts +6 -1
  145. package/src/lib/WazoSessionDescriptionHandler.ts +3 -3
  146. package/src/service/CallApi.ts +7 -7
  147. package/src/simple/Auth.ts +21 -16
  148. package/src/simple/Phone.ts +12 -11
  149. package/src/simple/__tests__/Auth.test.ts +9 -87
  150. package/src/simple/index.ts +2 -1
  151. package/src/simple/room/Room.ts +3 -3
  152. package/src/simple/room/SipRoom.ts +3 -3
  153. package/src/state-machine/call-state-machine.ts +2 -2
  154. package/src/state-machine/softphone-state-machine.ts +1 -2
  155. package/src/state-machine/utils.ts +12 -5
  156. package/src/utils/__tests__/api-requester.test.ts +20 -0
  157. package/src/utils/api-requester.ts +9 -1
  158. package/src/utils/sdp.ts +26 -11
  159. package/src/utils/webrtc.ts +27 -5
  160. package/src/voice/call.ts +169 -50
  161. package/src/voice/softphone.ts +180 -41
  162. 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, WazoSession } from '../domain/types';
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: WazoSession;
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: WazoSession) {
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;
@@ -1,9 +1,9 @@
1
- import Call from '../domain/Call';
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<Call[]> {
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(callSession: CallSession): Promise<boolean> {
41
- return getApiClient().calld.cancelCall(callSession.callId);
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<Call | null | undefined> {
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 Call.parse(response);
47
+ return ApiCall.parse(response);
48
48
  }
49
49
 
50
50
  static async relocateCall(callId: string, line: number, contactIdentifier?: string): Promise<Relocation> {
@@ -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
- response = await getApiClient().auth.initiateIdpAuthentication(domain, redirectUrl);
176
+ return await getApiClient().auth.initiateIdpAuthentication(domain, redirectUrl);
174
177
  } catch (e: any) {
175
- logger.error('Error during IdP authentication initiation:', e.message);
176
- return;
177
- }
178
+ logger.error('Error during IdP authentication initialization:', e.message);
178
179
 
179
- if (!response.ok) {
180
- if (response.status === 404) {
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 (response.status === 500) {
184
+ if (e.status === 500) {
185
185
  throw new SamlConfigError('SAML/server configuration error');
186
186
  }
187
187
 
188
- const error = await response.json();
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
- await getApiClient().auth.logOut(this.session.token);
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) {
@@ -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 WazoWebRTCClient, { events as clientEvents, transportEvents } from '../web-rtc-client';
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 { WazoSession, WebRtcConfig } from '../domain/types';
15
- import { getSipSessionId } from '../utils/sdp';
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: WazoWebRTCClient;
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 WazoWebRTCClient({
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
- getSipSessionId(sipSession: WazoSession): string | null | undefined {
333
+ getSipCallId(sipSession: SipCall): string | null | undefined {
333
334
  if (!sipSession || !this.phone) {
334
335
  return null;
335
336
  }
336
- return getSipSessionId(sipSession);
337
+ return getSipCallId(sipSession);
337
338
  }
338
339
 
339
- sendMessage(body: string, sipSession?: WazoSession, contentType = 'text/plain'): void {
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?: WazoSession): void {
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?: WazoSession): void {
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(): WazoSession | null {
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 ok', async () => {
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 an error `message` if the response is NOT ok', async () => {
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
- 'an error reason',
152
+ 'No route found for SAML SSO',
195
153
  );
196
154
  });
197
155
 
198
- it('should return an error with a null message if there is no `message` and `reason`', async () => {
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: {
@@ -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
@@ -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, WazoSession } from '../../domain/types';
13
- import { getSipSessionId } from '../../utils/sdp';
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 = getSipSessionId(Wazo.Phone.phone?.currentSipSession as WazoSession);
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 { WazoSession } from '../../domain/types';
7
- import { getSipSessionId } from '../../utils/sdp';
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 getSipSessionId(Wazo.Phone.phone?.currentSipSession as WazoSession);
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.getState() === SoftphoneStates.REGISTERED,
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
- // guard: 'isRegistered',
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.UNREGISTERED]: States.UNREGISTERED,
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
- return getState(actor) === state;
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
- return typeof btoa !== 'undefined' ? btoa(str) : Base64.encode(str);
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 getSipSessionId = (sipSession: SipCall | null | undefined): string => {
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.substring(0, SIP_ID_LENGTH);
157
+ return sipSession.outgoingRequestMessage.callId;
164
158
  }
165
159
 
166
160
  // For Invitation
167
- return (sipSession.id || '').substring(0, SIP_ID_LENGTH);
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
+ };
@@ -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.id,
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.id,
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 : constraints?.audio,
354
+ audio: screenSharing ? !isDesktop : withAudio ? getAudioConstraints(true) : false,
333
355
  video: screenSharing ? isDesktop ? {
334
356
  mandatory: {
335
357
  chromeMediaSource: 'desktop',