@wazo/sdk 0.43.5 → 1.0.0-alpha.0

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 (155) hide show
  1. package/dist/wazo-sdk.js +3 -3
  2. package/esm/__tests__/__voice__/call.test.js +72 -0
  3. package/esm/__tests__/__voice__/softphone.test.js +66 -0
  4. package/esm/api/auth.js +1 -5
  5. package/esm/checker/checks/webrtc.js +2 -1
  6. package/esm/domain/InvalidState.js +8 -0
  7. package/esm/domain/InvalidStateTransition.js +10 -0
  8. package/esm/domain/Phone/WebRTCPhone.js +33 -35
  9. package/esm/simple/Auth.js +12 -6
  10. package/esm/simple/Phone.js +2 -1
  11. package/esm/simple/__tests__/Auth.test.js +64 -8
  12. package/esm/simple/index.js +8 -2
  13. package/esm/simple/room/Room.js +2 -1
  14. package/esm/simple/room/SipRoom.js +2 -1
  15. package/esm/state-machine/call-state-machine.js +114 -0
  16. package/esm/state-machine/softphone-state-machine.js +39 -0
  17. package/esm/state-machine/utils.js +40 -0
  18. package/esm/types/__tests__/__voice__/call.test.d.ts +2 -0
  19. package/esm/types/__tests__/__voice__/call.test.d.ts.map +1 -0
  20. package/esm/types/__tests__/__voice__/softphone.test.d.ts +2 -0
  21. package/esm/types/__tests__/__voice__/softphone.test.d.ts.map +1 -0
  22. package/esm/types/api/auth.d.ts.map +1 -1
  23. package/esm/types/checker/checks/webrtc.d.ts.map +1 -1
  24. package/esm/types/domain/InvalidState.d.ts +6 -0
  25. package/esm/types/domain/InvalidState.d.ts.map +1 -0
  26. package/esm/types/domain/InvalidStateTransition.d.ts +7 -0
  27. package/esm/types/domain/InvalidStateTransition.d.ts.map +1 -0
  28. package/esm/types/domain/Phone/WebRTCPhone.d.ts +0 -1
  29. package/esm/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  30. package/esm/types/domain/types.d.ts +4 -0
  31. package/esm/types/domain/types.d.ts.map +1 -1
  32. package/esm/types/simple/Auth.d.ts.map +1 -1
  33. package/esm/types/simple/Phone.d.ts.map +1 -1
  34. package/esm/types/simple/index.d.ts +7 -2
  35. package/esm/types/simple/index.d.ts.map +1 -1
  36. package/esm/types/simple/room/Room.d.ts.map +1 -1
  37. package/esm/types/simple/room/SipRoom.d.ts +1 -1
  38. package/esm/types/simple/room/SipRoom.d.ts.map +1 -1
  39. package/esm/types/state-machine/call-state-machine.d.ts +43 -0
  40. package/esm/types/state-machine/call-state-machine.d.ts.map +1 -0
  41. package/esm/types/state-machine/softphone-state-machine.d.ts +19 -0
  42. package/esm/types/state-machine/softphone-state-machine.d.ts.map +1 -0
  43. package/esm/types/state-machine/utils.d.ts +9 -0
  44. package/esm/types/state-machine/utils.d.ts.map +1 -0
  45. package/esm/types/utils/api-requester.d.ts.map +1 -1
  46. package/esm/types/utils/sdp.d.ts +6 -0
  47. package/esm/types/utils/sdp.d.ts.map +1 -1
  48. package/esm/types/utils/sip-logger.d.ts +4 -0
  49. package/esm/types/utils/sip-logger.d.ts.map +1 -0
  50. package/esm/types/utils/webrtc.d.ts +40 -0
  51. package/esm/types/utils/webrtc.d.ts.map +1 -1
  52. package/esm/types/voice/call.d.ts +110 -0
  53. package/esm/types/voice/call.d.ts.map +1 -0
  54. package/esm/types/voice/softphone.d.ts +77 -0
  55. package/esm/types/voice/softphone.d.ts.map +1 -0
  56. package/esm/types/web-rtc-client.d.ts +1 -36
  57. package/esm/types/web-rtc-client.d.ts.map +1 -1
  58. package/esm/utils/__tests__/api-requester.test.js +0 -17
  59. package/esm/utils/api-requester.js +1 -9
  60. package/esm/utils/sdp.js +26 -0
  61. package/esm/utils/sip-logger.js +32 -0
  62. package/esm/utils/webrtc.js +465 -0
  63. package/esm/voice/call.js +527 -0
  64. package/esm/voice/softphone.js +347 -0
  65. package/esm/web-rtc-client.js +86 -584
  66. package/lib/__tests__/__voice__/call.test.js +86 -0
  67. package/lib/__tests__/__voice__/softphone.test.js +80 -0
  68. package/lib/api/auth.js +1 -5
  69. package/lib/checker/checks/webrtc.js +2 -1
  70. package/lib/domain/InvalidState.js +9 -0
  71. package/lib/domain/InvalidStateTransition.js +10 -0
  72. package/lib/domain/Phone/WebRTCPhone.js +33 -35
  73. package/lib/simple/Auth.js +12 -6
  74. package/lib/simple/Phone.js +2 -1
  75. package/lib/simple/__tests__/Auth.test.js +64 -11
  76. package/lib/simple/index.js +6 -0
  77. package/lib/simple/room/Room.js +4 -3
  78. package/lib/simple/room/SipRoom.js +2 -1
  79. package/lib/state-machine/call-state-machine.js +120 -0
  80. package/lib/state-machine/softphone-state-machine.js +42 -0
  81. package/lib/state-machine/utils.js +52 -0
  82. package/lib/types/__tests__/__voice__/call.test.d.ts +2 -0
  83. package/lib/types/__tests__/__voice__/call.test.d.ts.map +1 -0
  84. package/lib/types/__tests__/__voice__/softphone.test.d.ts +2 -0
  85. package/lib/types/__tests__/__voice__/softphone.test.d.ts.map +1 -0
  86. package/lib/types/api/auth.d.ts.map +1 -1
  87. package/lib/types/checker/checks/webrtc.d.ts.map +1 -1
  88. package/lib/types/domain/InvalidState.d.ts +6 -0
  89. package/lib/types/domain/InvalidState.d.ts.map +1 -0
  90. package/lib/types/domain/InvalidStateTransition.d.ts +7 -0
  91. package/lib/types/domain/InvalidStateTransition.d.ts.map +1 -0
  92. package/lib/types/domain/Phone/WebRTCPhone.d.ts +0 -1
  93. package/lib/types/domain/Phone/WebRTCPhone.d.ts.map +1 -1
  94. package/lib/types/domain/types.d.ts +4 -0
  95. package/lib/types/domain/types.d.ts.map +1 -1
  96. package/lib/types/simple/Auth.d.ts.map +1 -1
  97. package/lib/types/simple/Phone.d.ts.map +1 -1
  98. package/lib/types/simple/index.d.ts +7 -2
  99. package/lib/types/simple/index.d.ts.map +1 -1
  100. package/lib/types/simple/room/Room.d.ts.map +1 -1
  101. package/lib/types/simple/room/SipRoom.d.ts +1 -1
  102. package/lib/types/simple/room/SipRoom.d.ts.map +1 -1
  103. package/lib/types/state-machine/call-state-machine.d.ts +43 -0
  104. package/lib/types/state-machine/call-state-machine.d.ts.map +1 -0
  105. package/lib/types/state-machine/softphone-state-machine.d.ts +19 -0
  106. package/lib/types/state-machine/softphone-state-machine.d.ts.map +1 -0
  107. package/lib/types/state-machine/utils.d.ts +9 -0
  108. package/lib/types/state-machine/utils.d.ts.map +1 -0
  109. package/lib/types/utils/api-requester.d.ts.map +1 -1
  110. package/lib/types/utils/sdp.d.ts +6 -0
  111. package/lib/types/utils/sdp.d.ts.map +1 -1
  112. package/lib/types/utils/sip-logger.d.ts +4 -0
  113. package/lib/types/utils/sip-logger.d.ts.map +1 -0
  114. package/lib/types/utils/webrtc.d.ts +40 -0
  115. package/lib/types/utils/webrtc.d.ts.map +1 -1
  116. package/lib/types/voice/call.d.ts +110 -0
  117. package/lib/types/voice/call.d.ts.map +1 -0
  118. package/lib/types/voice/softphone.d.ts +77 -0
  119. package/lib/types/voice/softphone.d.ts.map +1 -0
  120. package/lib/types/web-rtc-client.d.ts +1 -36
  121. package/lib/types/web-rtc-client.d.ts.map +1 -1
  122. package/lib/utils/__tests__/api-requester.test.js +0 -17
  123. package/lib/utils/api-requester.js +1 -9
  124. package/lib/utils/sdp.js +27 -1
  125. package/lib/utils/sip-logger.js +37 -0
  126. package/lib/utils/webrtc.js +521 -1
  127. package/lib/voice/call.js +577 -0
  128. package/lib/voice/softphone.js +375 -0
  129. package/lib/web-rtc-client.js +89 -601
  130. package/package.json +3 -2
  131. package/src/__tests__/__voice__/call.test.ts +90 -0
  132. package/src/__tests__/__voice__/softphone.test.ts +82 -0
  133. package/src/api/auth.ts +2 -13
  134. package/src/checker/checks/webrtc.ts +2 -1
  135. package/src/domain/InvalidState.ts +13 -0
  136. package/src/domain/InvalidStateTransition.ts +17 -0
  137. package/src/domain/Phone/WebRTCPhone.ts +34 -36
  138. package/src/domain/types.ts +6 -1
  139. package/src/simple/Auth.ts +13 -6
  140. package/src/simple/Phone.ts +2 -1
  141. package/src/simple/__tests__/Auth.test.ts +87 -9
  142. package/src/simple/index.ts +9 -2
  143. package/src/simple/room/Room.ts +2 -1
  144. package/src/simple/room/SipRoom.ts +2 -1
  145. package/src/state-machine/call-state-machine.ts +131 -0
  146. package/src/state-machine/softphone-state-machine.ts +49 -0
  147. package/src/state-machine/utils.ts +53 -0
  148. package/src/utils/__tests__/api-requester.test.ts +0 -20
  149. package/src/utils/api-requester.ts +1 -9
  150. package/src/utils/sdp.ts +36 -0
  151. package/src/utils/sip-logger.ts +40 -0
  152. package/src/utils/webrtc.ts +571 -0
  153. package/src/voice/call.ts +713 -0
  154. package/src/voice/softphone.ts +485 -0
  155. package/src/web-rtc-client.ts +90 -690
@@ -1,5 +1,3 @@
1
- import BadResponse from '../../domain/BadResponse';
2
- import ServerError from '../../domain/ServerError';
3
1
  import getApiClient, { setRefreshToken } from '../../service/getApiClient';
4
2
  import { Auth as AuthClass } from '../Auth';
5
3
 
@@ -126,8 +124,13 @@ describe('simple/Auth', () => {
126
124
  describe('initiateIdpAuthentication', () => {
127
125
  const validResponse = { location: 'idp.com/auth', saml_session_id: 'a1b2C3d4' };
128
126
 
129
- it('should return a `location` and `saml_session_id` if the response is valid', async () => {
130
- const initiateIdpAuthentication = jest.fn(() => validResponse);
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
+ }));
131
134
 
132
135
  (getApiClient as any).mockImplementation(() => ({
133
136
  auth: {
@@ -138,9 +141,12 @@ describe('simple/Auth', () => {
138
141
  const response = await Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz');
139
142
  expect(response).toBe(validResponse);
140
143
  });
141
-
142
144
  it('should return a `NoSamlRouteError` if the response is a 404', async () => {
143
- const initiateIdpAuthentication = jest.fn(() => { throw BadResponse.fromText('an error message', 404); });
145
+ const initiateIdpAuthentication = jest.fn(() =>
146
+ Promise.resolve({
147
+ ok: false,
148
+ status: 404,
149
+ }));
144
150
 
145
151
  (getApiClient as any).mockImplementation(() => ({
146
152
  auth: {
@@ -149,12 +155,84 @@ describe('simple/Auth', () => {
149
155
  }));
150
156
 
151
157
  await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow(
152
- 'No route found for SAML SSO',
158
+ 'No route found for saml sso',
153
159
  );
154
160
  });
155
161
 
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); });
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
+ }));
186
+
187
+ (getApiClient as any).mockImplementation(() => ({
188
+ auth: {
189
+ initiateIdpAuthentication,
190
+ },
191
+ }));
192
+
193
+ await expect(Auth.initiateIdpAuthentication('example.com', 'https://myapp.xyz')).rejects.toThrow(
194
+ 'an error reason',
195
+ );
196
+ });
197
+
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
+ }));
158
236
 
159
237
  (getApiClient as any).mockImplementation(() => ({
160
238
  auth: {
@@ -17,7 +17,7 @@ import { CanceledCallError } from '../web-rtc-client';
17
17
  import BadResponse from '../domain/BadResponse';
18
18
  import ServerError from '../domain/ServerError';
19
19
  import SFUNotAvailableError from '../domain/SFUNotAvailableError';
20
- import Call from '../domain/Call';
20
+ import ApiCall from '../domain/Call';
21
21
  import CallLog from '../domain/CallLog';
22
22
  import Recording from '../domain/Recording';
23
23
  import ChatMessage from '../domain/ChatMessage';
@@ -41,6 +41,9 @@ 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';
45
+ import Call from '../voice/call';
46
+
44
47
  const Wazo = {
45
48
  Auth,
46
49
  Phone,
@@ -60,11 +63,15 @@ const Wazo = {
60
63
  loggerFor: IssueReporter.loggerFor.bind(IssueReporter),
61
64
  Features,
62
65
  Checker,
66
+ Voice: {
67
+ Softphone,
68
+ Call,
69
+ },
63
70
  // Domain
64
71
  domain: {
65
72
  BadResponse,
66
73
  ServerError,
67
- Call,
74
+ Call: ApiCall,
68
75
  CallLog,
69
76
  CTIPhone,
70
77
  Recording,
@@ -10,6 +10,7 @@ import RemoteParticipant from './RemoteParticipant';
10
10
  import IssueReporter from '../../service/IssueReporter';
11
11
  import LocalParticipant from './LocalParticipant';
12
12
  import { PeerConnection, WazoSession } from '../../domain/types';
13
+ import { getSipSessionId } from '../../utils/sdp';
13
14
 
14
15
  export const SIGNAL_TYPE_PARTICIPANT_UPDATE = 'signal/PARTICIPANT_UPDATE';
15
16
  export const SIGNAL_TYPE_PARTICIPANT_REQUEST = 'signal/PARTICIPANT_REQUEST';
@@ -497,7 +498,7 @@ class Room extends Emitter {
497
498
  this.audioStream = stream;
498
499
 
499
500
  if (!this.roomAudioElement) {
500
- const sessionId = Wazo.Phone.phone?.getSipSessionId(Wazo.Phone.phone?.currentSipSession as WazoSession);
501
+ const sessionId = getSipSessionId(Wazo.Phone.phone?.currentSipSession as WazoSession);
501
502
  this.roomAudioElement = await Wazo.Phone.phone?.createAudioElementFor(sessionId as string) as HTMLAudioElement;
502
503
  this.roomAudioElement.srcObject = stream;
503
504
  } else {
@@ -4,6 +4,7 @@ import IssueReporter from '../../service/IssueReporter';
4
4
  import Wazo from '../index';
5
5
  import Room from './Room';
6
6
  import { WazoSession } from '../../domain/types';
7
+ import { getSipSessionId } from '../../utils/sdp';
7
8
 
8
9
  const logger = IssueReporter.loggerFor('sdk-sip-room');
9
10
 
@@ -156,7 +157,7 @@ class SipRoom extends Room {
156
157
  }
157
158
 
158
159
  _getCurrentSipCallIs() {
159
- return Wazo.Phone.getSipSessionId(Wazo.Phone.phone?.currentSipSession as WazoSession);
160
+ return getSipSessionId(Wazo.Phone.phone?.currentSipSession as WazoSession);
160
161
  }
161
162
 
162
163
  }
@@ -0,0 +1,131 @@
1
+ import { setup, type ActorRefFrom } from 'xstate';
2
+
3
+ import softphone from '../voice/softphone';
4
+ import { States as SoftphoneStates } from './softphone-state-machine';
5
+
6
+ export const States = {
7
+ IDLE: 'idle',
8
+ RINGING: 'ringing',
9
+ IN_PROGRESS: 'progress',
10
+ ESTABLISHED: 'established',
11
+ TERMINATED: 'terminated',
12
+ } as const;
13
+
14
+ export const EstablishedStates = {
15
+ ONGOING: 'ongoing',
16
+ HELD: 'held',
17
+ MUTED: 'muted',
18
+ } as const;
19
+
20
+ export type StateTypes = typeof States[keyof typeof States];
21
+
22
+ export type EstablishedStateTypes = typeof EstablishedStates[keyof typeof EstablishedStates];
23
+
24
+ export const Actions = {
25
+ INCOMING_CALL: 'incomingCall',
26
+ MAKE_CALL: 'makeCall',
27
+ ACCEPT: 'accept',
28
+ REMOTLY_ACCEPTED: 'remotlyAccepted',
29
+ REJECT: 'reject',
30
+ HANGUP: 'hangup',
31
+ } as const;
32
+
33
+ export const EstablishedActions = {
34
+ HOLD: 'hold',
35
+ RESUME: 'resume',
36
+ MUTE: 'mute',
37
+ UN_MUTE: 'unMute',
38
+ } as const;
39
+
40
+ export type ActionTypes = typeof Actions[keyof typeof Actions];
41
+
42
+ export type EstablishedActionTypes = typeof EstablishedActions[keyof typeof EstablishedActions];
43
+
44
+ const callMachine = setup({
45
+ guards: {
46
+ isRegistered: () => softphone.getState() === SoftphoneStates.REGISTERED,
47
+ },
48
+ }).createMachine({
49
+ id: 'call',
50
+ initial: States.IDLE,
51
+ context: {},
52
+ states: {
53
+ [States.IDLE]: {
54
+ on: {
55
+ [Actions.INCOMING_CALL]: {
56
+ // guard: 'isRegistered',
57
+ target: States.RINGING,
58
+ },
59
+ [Actions.MAKE_CALL]: {
60
+ guard: 'isRegistered',
61
+ target: States.IN_PROGRESS,
62
+ },
63
+ },
64
+ },
65
+ [States.IN_PROGRESS]: {
66
+ on: {
67
+ [Actions.HANGUP]: {
68
+ guard: 'isRegistered',
69
+ target: States.TERMINATED,
70
+ },
71
+ [Actions.REMOTLY_ACCEPTED]: {
72
+ guard: 'isRegistered',
73
+ target: States.ESTABLISHED,
74
+ },
75
+ },
76
+ },
77
+ [States.RINGING]: {
78
+ on: {
79
+ [Actions.ACCEPT]: {
80
+ guard: 'isRegistered',
81
+ target: States.ESTABLISHED,
82
+ },
83
+ [Actions.REJECT]: {
84
+ guard: 'isRegistered',
85
+ target: States.TERMINATED,
86
+ },
87
+ },
88
+ },
89
+ [States.ESTABLISHED]: {
90
+ initial: EstablishedStates.ONGOING,
91
+ on: {
92
+ [Actions.HANGUP]: {
93
+ guard: 'isRegistered',
94
+ target: States.TERMINATED,
95
+ },
96
+ [EstablishedActions.HOLD]: {
97
+ guard: 'isRegistered',
98
+ target: `.${EstablishedStates.HELD}`,
99
+ },
100
+ [EstablishedActions.MUTE]: {
101
+ guard: 'isRegistered',
102
+ target: `.${EstablishedStates.MUTED}`,
103
+ },
104
+ },
105
+ states: {
106
+ [EstablishedStates.ONGOING]: { },
107
+ [EstablishedStates.HELD]: {
108
+ on: {
109
+ [EstablishedActions.RESUME]: {
110
+ guard: 'isRegistered',
111
+ target: EstablishedStates.ONGOING,
112
+ },
113
+ },
114
+ },
115
+ [EstablishedStates.MUTED]: {
116
+ on: {
117
+ [EstablishedActions.UN_MUTE]: {
118
+ guard: 'isRegistered',
119
+ target: EstablishedStates.ONGOING,
120
+ },
121
+ },
122
+ },
123
+ },
124
+ },
125
+ [States.TERMINATED]: { },
126
+ },
127
+ });
128
+
129
+ export type CallActorRef = ActorRefFrom<typeof callMachine>;
130
+
131
+ export default callMachine;
@@ -0,0 +1,49 @@
1
+ import { type ActorRefFrom, setup } from 'xstate';
2
+
3
+ export const States = {
4
+ UNREGISTERED: 'unregistered',
5
+ REGISTERING: 'registering',
6
+ REGISTERED: 'registered',
7
+ } as const;
8
+
9
+ export type StateTypes = typeof States[keyof typeof States];
10
+
11
+ export const Actions = {
12
+ REGISTER: 'register',
13
+ REGISTER_DONE: 'registerDone',
14
+ UNREGISTER: 'unregister',
15
+ UNREGISTERED: 'unregistered',
16
+ TRANSPORT_CLOSED: 'transportClosed',
17
+ } as const;
18
+
19
+ export type ActionTypes = typeof Actions[keyof typeof Actions];
20
+
21
+ const softphoneMachine = setup({}).createMachine({
22
+ id: 'softphone',
23
+ initial: States.UNREGISTERED,
24
+ context: {},
25
+ states: {
26
+ [States.UNREGISTERED]: {
27
+ on: {
28
+ [Actions.REGISTER]: States.REGISTERING,
29
+ },
30
+ },
31
+ [States.REGISTERING]: {
32
+ on: {
33
+ [Actions.REGISTER_DONE]: States.REGISTERED,
34
+ [Actions.TRANSPORT_CLOSED]: States.UNREGISTERED,
35
+ [Actions.UNREGISTERED]: States.UNREGISTERED,
36
+ },
37
+ },
38
+ [States.REGISTERED]: {
39
+ on: {
40
+ [Actions.UNREGISTER]: States.UNREGISTERED,
41
+ [Actions.TRANSPORT_CLOSED]: States.UNREGISTERED,
42
+ },
43
+ },
44
+ },
45
+ });
46
+
47
+ export type SoftphoneActorRef = ActorRefFrom<typeof softphoneMachine>;
48
+
49
+ export default softphoneMachine;
@@ -0,0 +1,53 @@
1
+ import { type SnapshotFrom } from 'xstate';
2
+ import { type SoftphoneActorRef, type ActionTypes as SoftphoneActions, type StateTypes as SoftphoneStates } from './softphone-state-machine';
3
+ import { type CallActorRef, type ActionTypes as CallActions, type EstablishedActionTypes as EstablishedCallActions, type StateTypes as CallStates, type EstablishedStateTypes } from './call-state-machine';
4
+
5
+ import InvalidStateTransition from '../domain/InvalidStateTransition';
6
+ import InvalidState from '../domain/InvalidState';
7
+ import IssueReporter from '../service/IssueReporter';
8
+
9
+ const logger = IssueReporter.loggerFor('state-machine');
10
+
11
+ export const getState = (actor: SoftphoneActorRef | CallActorRef): SoftphoneStates | CallStates | EstablishedStateTypes => {
12
+ return actor.getSnapshot().value as SoftphoneStates | CallStates | EstablishedStateTypes;
13
+ };
14
+
15
+ export const can = (actor: SoftphoneActorRef | CallActorRef, action: SoftphoneActions | CallActions | EstablishedCallActions): boolean => {
16
+ return actor.getSnapshot().can({ type: action });
17
+ };
18
+
19
+ export const hasState = (actor: SoftphoneActorRef | CallActorRef, state: SoftphoneStates | CallStates): boolean => {
20
+ return getState(actor) === state;
21
+ };
22
+
23
+ export const waitUntilState = (actor: SoftphoneActorRef | CallActorRef, state: SoftphoneStates | CallStates, timeout = 5000): Promise<unknown> => {
24
+ return Promise.race([
25
+ new Promise(resolve => {
26
+ (actor as SoftphoneActorRef).subscribe((snapshot: SnapshotFrom<SoftphoneActorRef | CallActorRef>) => {
27
+ if (snapshot.value === state) {
28
+ resolve(null);
29
+ }
30
+ });
31
+ }),
32
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Waited for state ${state} timeout.`)), timeout)),
33
+ ]);
34
+ };
35
+
36
+ export const assertCan = (actor: SoftphoneActorRef | CallActorRef, action: SoftphoneActions | CallActions | EstablishedCallActions): void => {
37
+ if (!can(actor, action)) {
38
+ const currentState = actor.getSnapshot().value;
39
+ const message = `Invalid state transition from ${currentState} with action ${action}`;
40
+ logger.warn(message);
41
+
42
+ throw new InvalidStateTransition(message, action, currentState as string);
43
+ }
44
+ };
45
+
46
+ export const assertState = (actor: SoftphoneActorRef | CallActorRef, state: SoftphoneStates | CallStates): void => {
47
+ if (!hasState(actor, state)) {
48
+ const message = `Invalid state ${state}`;
49
+ logger.warn(message);
50
+
51
+ throw new InvalidState(message, state);
52
+ }
53
+ };
@@ -158,23 +158,3 @@ 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,15 +64,7 @@ export default class ApiRequester {
64
64
  }
65
65
 
66
66
  static base64Encode(str: string): string {
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);
67
+ return typeof btoa !== 'undefined' ? btoa(str) : Base64.encode(str);
76
68
  }
77
69
 
78
70
  // @see https://github.com/facebook/flow/issues/183#issuecomment-358607052
package/src/utils/sdp.ts CHANGED
@@ -1,4 +1,11 @@
1
1
  import sdpParser from 'sdp-transform';
2
+ import { UserAgent } from 'sip.js/lib/api/user-agent';
3
+ import { Inviter } from 'sip.js/lib/api';
4
+ import { URI } from 'sip.js/lib/grammar/uri';
5
+
6
+ import type { SipCall } from '../domain/types';
7
+
8
+ export const SIP_ID_LENGTH = 36;
2
9
 
3
10
  export type Candidate = {
4
11
  foundation: string;
@@ -138,3 +145,32 @@ export const addIcesInAllBundles = (sdp: string) => {
138
145
  }));
139
146
  return sdpParser.write(parsedSdp);
140
147
  };
148
+
149
+ export const getSipSessionId = (sipSession: SipCall | null | undefined): string => {
150
+ if (!sipSession) {
151
+ return '';
152
+ }
153
+
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
+ // For Inviter
160
+ // @ts-ignore: private
161
+ if (sipSession instanceof Inviter && sipSession.outgoingRequestMessage) {
162
+ // @ts-ignore: private
163
+ return sipSession.outgoingRequestMessage.callId.substring(0, SIP_ID_LENGTH);
164
+ }
165
+
166
+ // For Invitation
167
+ return (sipSession.id || '').substring(0, SIP_ID_LENGTH);
168
+ };
169
+
170
+ // We need to replace 0.0.0.0 to 127.0.0.1 in the sdp to avoid MOH during a createOffer.
171
+ export const replaceLocalIpModifier = (description: Record<string, any>) => Promise.resolve({ // description is immutable... so we have to clone it or the `type` attribute won't be returned.
172
+ ...JSON.parse(JSON.stringify(description)),
173
+ sdp: description.sdp.replace('c=IN IP4 0.0.0.0', 'c=IN IP4 127.0.0.1'),
174
+ });
175
+
176
+ export const makeURI = (target: string, host: string): URI | undefined => UserAgent.makeURI(`sip:${target}@${host}`);
@@ -0,0 +1,40 @@
1
+ import IssueReporter from '../service/IssueReporter';
2
+ import { WebRtcConfig } from '../domain/types';
3
+
4
+ const sipLogger = IssueReporter.loggerFor('sip.js');
5
+ const protocolLogger = IssueReporter.loggerFor('sip');
6
+ const protocolDebugMessages = ['Received WebSocket text message:', 'Sending WebSocket message:'];
7
+
8
+ const logConnector = (level: any, className: string, label: any, content: string): void => {
9
+ const protocolIndex = content && content.indexOf ? protocolDebugMessages.findIndex(prefix => content.indexOf(prefix) !== -1) : -1;
10
+
11
+ if (className === 'sip.Transport' && protocolIndex !== -1) {
12
+ const direction = protocolIndex === 0 ? 'receiving' : 'sending';
13
+ const message = content.replace(`${protocolDebugMessages[protocolIndex]}\n\n`, '').replace('\r\n', '\n');
14
+ protocolLogger.trace(message, {
15
+ className,
16
+ direction,
17
+ });
18
+ } else {
19
+ sipLogger.trace(content, {
20
+ className,
21
+ });
22
+ }
23
+ };
24
+
25
+ const configureLogger = (rawOptions: Partial<WebRtcConfig> = {}): Partial<WebRtcConfig> => {
26
+ const options = rawOptions;
27
+
28
+ if (options.uaConfigOverrides) {
29
+ options.uaConfigOverrides.traceSip = true;
30
+ }
31
+ options.log = options.log || {};
32
+ options.log.builtinEnabled = false;
33
+ options.log.logLevel = 'debug';
34
+
35
+ options.log.connector = logConnector;
36
+
37
+ return options;
38
+ };
39
+
40
+ export default configureLogger;