@webex/calling 0.0.1-next.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 (179) hide show
  1. package/README.md +67 -0
  2. package/dist/module/CallHistory/CallHistory.js +84 -0
  3. package/dist/module/CallHistory/callHistoryFixtures.js +307 -0
  4. package/dist/module/CallHistory/constants.js +9 -0
  5. package/dist/module/CallHistory/types.js +1 -0
  6. package/dist/module/CallSettings/CallSettings.js +65 -0
  7. package/dist/module/CallSettings/UcmBackendConnector.js +100 -0
  8. package/dist/module/CallSettings/WxCallBackendConnector.js +287 -0
  9. package/dist/module/CallSettings/constants.js +11 -0
  10. package/dist/module/CallSettings/testFixtures.js +62 -0
  11. package/dist/module/CallSettings/types.js +1 -0
  12. package/dist/module/CallingClient/CallingClient.js +268 -0
  13. package/dist/module/CallingClient/callRecordFixtures.js +93 -0
  14. package/dist/module/CallingClient/calling/CallerId/index.js +169 -0
  15. package/dist/module/CallingClient/calling/CallerId/types.js +1 -0
  16. package/dist/module/CallingClient/calling/call.js +1649 -0
  17. package/dist/module/CallingClient/calling/callManager.js +274 -0
  18. package/dist/module/CallingClient/calling/index.js +2 -0
  19. package/dist/module/CallingClient/calling/types.js +53 -0
  20. package/dist/module/CallingClient/callingClientFixtures.js +38 -0
  21. package/dist/module/CallingClient/constants.js +122 -0
  22. package/dist/module/CallingClient/line/index.js +110 -0
  23. package/dist/module/CallingClient/line/types.js +14 -0
  24. package/dist/module/CallingClient/registration/index.js +1 -0
  25. package/dist/module/CallingClient/registration/register.js +507 -0
  26. package/dist/module/CallingClient/registration/registerFixtures.js +28 -0
  27. package/dist/module/CallingClient/registration/types.js +1 -0
  28. package/dist/module/CallingClient/types.js +1 -0
  29. package/dist/module/Contacts/ContactsClient.js +487 -0
  30. package/dist/module/Contacts/constants.js +20 -0
  31. package/dist/module/Contacts/contactFixtures.js +284 -0
  32. package/dist/module/Contacts/types.js +10 -0
  33. package/dist/module/Errors/catalog/CallError.js +26 -0
  34. package/dist/module/Errors/catalog/CallingDeviceError.js +18 -0
  35. package/dist/module/Errors/catalog/ExtendedError.js +10 -0
  36. package/dist/module/Errors/catalog/LineError.js +24 -0
  37. package/dist/module/Errors/index.js +2 -0
  38. package/dist/module/Errors/types.js +48 -0
  39. package/dist/module/Events/impl/index.js +19 -0
  40. package/dist/module/Events/types.js +74 -0
  41. package/dist/module/Logger/index.js +114 -0
  42. package/dist/module/Logger/types.js +25 -0
  43. package/dist/module/Metrics/index.js +232 -0
  44. package/dist/module/Metrics/types.js +37 -0
  45. package/dist/module/SDKConnector/index.js +39 -0
  46. package/dist/module/SDKConnector/types.js +1 -0
  47. package/dist/module/SDKConnector/utils.js +12 -0
  48. package/dist/module/Voicemail/BroadworksBackendConnector.js +289 -0
  49. package/dist/module/Voicemail/UcmBackendConnector.js +275 -0
  50. package/dist/module/Voicemail/Voicemail.js +110 -0
  51. package/dist/module/Voicemail/WxCallBackendConnector.js +279 -0
  52. package/dist/module/Voicemail/constants.js +29 -0
  53. package/dist/module/Voicemail/types.js +1 -0
  54. package/dist/module/Voicemail/voicemailFixture.js +449 -0
  55. package/dist/module/common/Utils.js +802 -0
  56. package/dist/module/common/constants.js +40 -0
  57. package/dist/module/common/index.js +1 -0
  58. package/dist/module/common/testUtil.js +938 -0
  59. package/dist/module/common/types.js +57 -0
  60. package/dist/module/index.js +8 -0
  61. package/dist/types/CallHistory/CallHistory.d.ts +19 -0
  62. package/dist/types/CallHistory/CallHistory.d.ts.map +1 -0
  63. package/dist/types/CallHistory/callHistoryFixtures.d.ts +95 -0
  64. package/dist/types/CallHistory/callHistoryFixtures.d.ts.map +1 -0
  65. package/dist/types/CallHistory/constants.d.ts +10 -0
  66. package/dist/types/CallHistory/constants.d.ts.map +1 -0
  67. package/dist/types/CallHistory/types.d.ts +21 -0
  68. package/dist/types/CallHistory/types.d.ts.map +1 -0
  69. package/dist/types/CallSettings/CallSettings.d.ts +20 -0
  70. package/dist/types/CallSettings/CallSettings.d.ts.map +1 -0
  71. package/dist/types/CallSettings/UcmBackendConnector.d.ts +20 -0
  72. package/dist/types/CallSettings/UcmBackendConnector.d.ts.map +1 -0
  73. package/dist/types/CallSettings/WxCallBackendConnector.d.ts +22 -0
  74. package/dist/types/CallSettings/WxCallBackendConnector.d.ts.map +1 -0
  75. package/dist/types/CallSettings/constants.d.ts +12 -0
  76. package/dist/types/CallSettings/constants.d.ts.map +1 -0
  77. package/dist/types/CallSettings/testFixtures.d.ts +16 -0
  78. package/dist/types/CallSettings/testFixtures.d.ts.map +1 -0
  79. package/dist/types/CallSettings/types.d.ts +108 -0
  80. package/dist/types/CallSettings/types.d.ts.map +1 -0
  81. package/dist/types/CallingClient/CallingClient.d.ts +38 -0
  82. package/dist/types/CallingClient/CallingClient.d.ts.map +1 -0
  83. package/dist/types/CallingClient/callRecordFixtures.d.ts +4 -0
  84. package/dist/types/CallingClient/callRecordFixtures.d.ts.map +1 -0
  85. package/dist/types/CallingClient/calling/CallerId/index.d.ts +18 -0
  86. package/dist/types/CallingClient/calling/CallerId/index.d.ts.map +1 -0
  87. package/dist/types/CallingClient/calling/CallerId/types.d.ts +42 -0
  88. package/dist/types/CallingClient/calling/CallerId/types.d.ts.map +1 -0
  89. package/dist/types/CallingClient/calling/call.d.ts +95 -0
  90. package/dist/types/CallingClient/calling/call.d.ts.map +1 -0
  91. package/dist/types/CallingClient/calling/callManager.d.ts +22 -0
  92. package/dist/types/CallingClient/calling/callManager.d.ts.map +1 -0
  93. package/dist/types/CallingClient/calling/index.d.ts +3 -0
  94. package/dist/types/CallingClient/calling/index.d.ts.map +1 -0
  95. package/dist/types/CallingClient/calling/types.d.ts +204 -0
  96. package/dist/types/CallingClient/calling/types.d.ts.map +1 -0
  97. package/dist/types/CallingClient/callingClientFixtures.d.ts +19 -0
  98. package/dist/types/CallingClient/callingClientFixtures.d.ts.map +1 -0
  99. package/dist/types/CallingClient/constants.d.ts +123 -0
  100. package/dist/types/CallingClient/constants.d.ts.map +1 -0
  101. package/dist/types/CallingClient/line/index.d.ts +39 -0
  102. package/dist/types/CallingClient/line/index.d.ts.map +1 -0
  103. package/dist/types/CallingClient/line/types.d.ts +51 -0
  104. package/dist/types/CallingClient/line/types.d.ts.map +1 -0
  105. package/dist/types/CallingClient/registration/index.d.ts +2 -0
  106. package/dist/types/CallingClient/registration/index.d.ts.map +1 -0
  107. package/dist/types/CallingClient/registration/register.d.ts +65 -0
  108. package/dist/types/CallingClient/registration/register.d.ts.map +1 -0
  109. package/dist/types/CallingClient/registration/registerFixtures.d.ts +29 -0
  110. package/dist/types/CallingClient/registration/registerFixtures.d.ts.map +1 -0
  111. package/dist/types/CallingClient/registration/types.d.ts +21 -0
  112. package/dist/types/CallingClient/registration/types.d.ts.map +1 -0
  113. package/dist/types/CallingClient/types.d.ts +30 -0
  114. package/dist/types/CallingClient/types.d.ts.map +1 -0
  115. package/dist/types/Contacts/ContactsClient.d.ts +28 -0
  116. package/dist/types/Contacts/ContactsClient.d.ts.map +1 -0
  117. package/dist/types/Contacts/constants.d.ts +20 -0
  118. package/dist/types/Contacts/constants.d.ts.map +1 -0
  119. package/dist/types/Contacts/contactFixtures.d.ts +281 -0
  120. package/dist/types/Contacts/contactFixtures.d.ts.map +1 -0
  121. package/dist/types/Contacts/types.d.ts +75 -0
  122. package/dist/types/Contacts/types.d.ts.map +1 -0
  123. package/dist/types/Errors/catalog/CallError.d.ts +12 -0
  124. package/dist/types/Errors/catalog/CallError.d.ts.map +1 -0
  125. package/dist/types/Errors/catalog/CallingDeviceError.d.ts +11 -0
  126. package/dist/types/Errors/catalog/CallingDeviceError.d.ts.map +1 -0
  127. package/dist/types/Errors/catalog/ExtendedError.d.ts +7 -0
  128. package/dist/types/Errors/catalog/ExtendedError.d.ts.map +1 -0
  129. package/dist/types/Errors/catalog/LineError.d.ts +11 -0
  130. package/dist/types/Errors/catalog/LineError.d.ts.map +1 -0
  131. package/dist/types/Errors/index.d.ts +3 -0
  132. package/dist/types/Errors/index.d.ts.map +1 -0
  133. package/dist/types/Errors/types.d.ts +61 -0
  134. package/dist/types/Errors/types.d.ts.map +1 -0
  135. package/dist/types/Events/impl/index.d.ts +9 -0
  136. package/dist/types/Events/impl/index.d.ts.map +1 -0
  137. package/dist/types/Events/types.d.ts +284 -0
  138. package/dist/types/Events/types.d.ts.map +1 -0
  139. package/dist/types/Logger/index.d.ts +13 -0
  140. package/dist/types/Logger/index.d.ts.map +1 -0
  141. package/dist/types/Logger/types.d.ts +26 -0
  142. package/dist/types/Logger/types.d.ts.map +1 -0
  143. package/dist/types/Metrics/index.d.ts +6 -0
  144. package/dist/types/Metrics/index.d.ts.map +1 -0
  145. package/dist/types/Metrics/types.d.ts +43 -0
  146. package/dist/types/Metrics/types.d.ts.map +1 -0
  147. package/dist/types/SDKConnector/index.d.ts +13 -0
  148. package/dist/types/SDKConnector/index.d.ts.map +1 -0
  149. package/dist/types/SDKConnector/types.d.ts +129 -0
  150. package/dist/types/SDKConnector/types.d.ts.map +1 -0
  151. package/dist/types/SDKConnector/utils.d.ts +6 -0
  152. package/dist/types/SDKConnector/utils.d.ts.map +1 -0
  153. package/dist/types/Voicemail/BroadworksBackendConnector.d.ts +28 -0
  154. package/dist/types/Voicemail/BroadworksBackendConnector.d.ts.map +1 -0
  155. package/dist/types/Voicemail/UcmBackendConnector.d.ts +35 -0
  156. package/dist/types/Voicemail/UcmBackendConnector.d.ts.map +1 -0
  157. package/dist/types/Voicemail/Voicemail.d.ts +28 -0
  158. package/dist/types/Voicemail/Voicemail.d.ts.map +1 -0
  159. package/dist/types/Voicemail/WxCallBackendConnector.d.ts +24 -0
  160. package/dist/types/Voicemail/WxCallBackendConnector.d.ts.map +1 -0
  161. package/dist/types/Voicemail/constants.d.ts +30 -0
  162. package/dist/types/Voicemail/constants.d.ts.map +1 -0
  163. package/dist/types/Voicemail/types.d.ts +134 -0
  164. package/dist/types/Voicemail/types.d.ts.map +1 -0
  165. package/dist/types/Voicemail/voicemailFixture.d.ts +350 -0
  166. package/dist/types/Voicemail/voicemailFixture.d.ts.map +1 -0
  167. package/dist/types/common/Utils.d.ts +35 -0
  168. package/dist/types/common/Utils.d.ts.map +1 -0
  169. package/dist/types/common/constants.d.ts +41 -0
  170. package/dist/types/common/constants.d.ts.map +1 -0
  171. package/dist/types/common/index.d.ts +2 -0
  172. package/dist/types/common/index.d.ts.map +1 -0
  173. package/dist/types/common/testUtil.d.ts +3612 -0
  174. package/dist/types/common/testUtil.d.ts.map +1 -0
  175. package/dist/types/common/types.d.ts +192 -0
  176. package/dist/types/common/types.d.ts.map +1 -0
  177. package/dist/types/index.d.ts +9 -0
  178. package/dist/types/index.d.ts.map +1 -0
  179. package/package.json +161 -0
@@ -0,0 +1,1649 @@
1
+ import { Event, RoapMediaConnection } from '@webex/internal-media-core';
2
+ import { createMachine, interpret } from 'xstate';
3
+ import { v4 as uuid } from 'uuid';
4
+ import { ERROR_LAYER, ERROR_TYPE } from '../../Errors/types';
5
+ import { handleCallErrors, parseMediaQualityStatistics } from '../../common/Utils';
6
+ import { ALLOWED_SERVICES, CallDirection, HTTP_METHODS, } from '../../common/types';
7
+ import { createCallError } from '../../Errors/catalog/CallError';
8
+ import { CALL_ENDPOINT_RESOURCE, CALL_FILE, CALL_HOLD_SERVICE, CALL_STATUS_RESOURCE, CALL_TRANSFER_SERVICE, CALLING_USER_AGENT, CALLS_ENDPOINT_RESOURCE, CISCO_DEVICE_URL, DEFAULT_LOCAL_CALL_ID, DEFAULT_SESSION_TIMER, DEVICES_ENDPOINT_RESOURCE, HOLD_ENDPOINT, INITIAL_SEQ_NUMBER, MEDIA_ENDPOINT_RESOURCE, RESUME_ENDPOINT, SPARK_USER_AGENT, SUPPLEMENTARY_SERVICES_TIMEOUT, TRANSFER_ENDPOINT, } from '../constants';
9
+ import SDKConnector from '../../SDKConnector';
10
+ import { Eventing } from '../../Events/impl';
11
+ import { EVENT_KEYS, MEDIA_CONNECTION_EVENT_KEYS, MOBIUS_MIDCALL_STATE, SUPPLEMENTARY_SERVICES, } from '../../Events/types';
12
+ import { DisconnectCause, DisconnectCode, MidCallEventType, MobiusCallState, RoapScenario, TransferType, } from './types';
13
+ import log from '../../Logger';
14
+ import { createCallerId } from './CallerId';
15
+ import { METRIC_TYPE, METRIC_EVENT, TRANSFER_ACTION } from '../../Metrics/types';
16
+ import { getMetricManager } from '../../Metrics';
17
+ import { SERVICES_ENDPOINT } from '../../common/constants';
18
+ export class Call extends Eventing {
19
+ sdkConnector;
20
+ webex;
21
+ destination;
22
+ direction;
23
+ callId;
24
+ correlationId;
25
+ deviceId;
26
+ disconnectReason;
27
+ callStateMachine;
28
+ mediaStateMachine;
29
+ seq;
30
+ mediaConnection;
31
+ earlyMedia;
32
+ connected;
33
+ callerInfo;
34
+ localRoapMessage;
35
+ mobiusUrl;
36
+ remoteRoapMessage;
37
+ deleteCb;
38
+ callerId;
39
+ sessionTimer;
40
+ supplementaryServicesTimer;
41
+ muted;
42
+ held;
43
+ metricManager;
44
+ broadworksCorrelationInfo;
45
+ serviceIndicator;
46
+ mediaNegotiationCompleted;
47
+ receivedRoapOKSeq;
48
+ isMuted() {
49
+ return this.muted;
50
+ }
51
+ isConnected() {
52
+ return this.connected;
53
+ }
54
+ isHeld() {
55
+ return this.held;
56
+ }
57
+ constructor(activeUrl, webex, destination, direction, deviceId, deleteCb, indicator) {
58
+ super();
59
+ this.destination = destination;
60
+ this.direction = direction;
61
+ this.sdkConnector = SDKConnector;
62
+ this.deviceId = deviceId;
63
+ this.serviceIndicator = indicator;
64
+ if (!this.sdkConnector.getWebex()) {
65
+ SDKConnector.setWebex(webex);
66
+ }
67
+ this.webex = this.sdkConnector.getWebex();
68
+ this.metricManager = getMetricManager(this.webex, this.serviceIndicator);
69
+ this.callId = `${DEFAULT_LOCAL_CALL_ID}_${uuid()}`;
70
+ this.correlationId = uuid();
71
+ this.deleteCb = deleteCb;
72
+ this.connected = false;
73
+ this.held = false;
74
+ this.earlyMedia = false;
75
+ this.callerInfo = {};
76
+ this.localRoapMessage = {};
77
+ this.mobiusUrl = activeUrl;
78
+ this.receivedRoapOKSeq = 0;
79
+ this.mediaNegotiationCompleted = false;
80
+ log.info(`Mobius Url:- ${this.mobiusUrl}`, {
81
+ file: CALL_FILE,
82
+ method: 'constructor',
83
+ });
84
+ this.seq = INITIAL_SEQ_NUMBER;
85
+ this.callerId = createCallerId(webex, (callerInfo) => {
86
+ this.callerInfo = callerInfo;
87
+ const emitObj = {
88
+ correlationId: this.correlationId,
89
+ callerId: this.callerInfo,
90
+ };
91
+ this.emit(EVENT_KEYS.CALLER_ID, emitObj);
92
+ });
93
+ this.remoteRoapMessage = null;
94
+ this.disconnectReason = { code: DisconnectCode.NORMAL, cause: DisconnectCause.NORMAL };
95
+ const callMachine = createMachine({
96
+ schema: {
97
+ context: {},
98
+ events: {},
99
+ },
100
+ id: 'call-state',
101
+ initial: 'S_IDLE',
102
+ context: {},
103
+ states: {
104
+ S_IDLE: {
105
+ on: {
106
+ E_RECV_CALL_SETUP: {
107
+ target: 'S_RECV_CALL_SETUP',
108
+ actions: ['incomingCallSetup'],
109
+ },
110
+ E_SEND_CALL_SETUP: {
111
+ target: 'S_SEND_CALL_SETUP',
112
+ actions: ['outgoingCallSetup'],
113
+ },
114
+ E_RECV_CALL_DISCONNECT: {
115
+ target: 'S_RECV_CALL_DISCONNECT',
116
+ actions: ['incomingCallDisconnect'],
117
+ },
118
+ E_SEND_CALL_DISCONNECT: {
119
+ target: 'S_SEND_CALL_DISCONNECT',
120
+ actions: ['outgoingCallDisconnect'],
121
+ },
122
+ E_UNKNOWN: {
123
+ target: 'S_UNKNOWN',
124
+ actions: ['unknownState'],
125
+ },
126
+ },
127
+ },
128
+ S_RECV_CALL_SETUP: {
129
+ on: {
130
+ E_SEND_CALL_ALERTING: {
131
+ target: 'S_SEND_CALL_PROGRESS',
132
+ actions: ['outgoingCallAlerting'],
133
+ },
134
+ E_RECV_CALL_DISCONNECT: {
135
+ target: 'S_RECV_CALL_DISCONNECT',
136
+ actions: ['incomingCallDisconnect'],
137
+ },
138
+ E_SEND_CALL_DISCONNECT: {
139
+ target: 'S_SEND_CALL_DISCONNECT',
140
+ actions: ['outgoingCallDisconnect'],
141
+ },
142
+ E_UNKNOWN: {
143
+ target: 'S_UNKNOWN',
144
+ actions: ['unknownState'],
145
+ },
146
+ },
147
+ },
148
+ S_SEND_CALL_SETUP: {
149
+ on: {
150
+ E_RECV_CALL_PROGRESS: {
151
+ target: 'S_RECV_CALL_PROGRESS',
152
+ actions: ['incomingCallProgress'],
153
+ },
154
+ E_RECV_CALL_DISCONNECT: {
155
+ target: 'S_RECV_CALL_DISCONNECT',
156
+ actions: ['incomingCallDisconnect'],
157
+ },
158
+ E_SEND_CALL_DISCONNECT: {
159
+ target: 'S_SEND_CALL_DISCONNECT',
160
+ actions: ['outgoingCallDisconnect'],
161
+ },
162
+ E_UNKNOWN: {
163
+ target: 'S_UNKNOWN',
164
+ actions: ['unknownState'],
165
+ },
166
+ },
167
+ },
168
+ S_RECV_CALL_PROGRESS: {
169
+ on: {
170
+ E_RECV_CALL_CONNECT: {
171
+ target: 'S_RECV_CALL_CONNECT',
172
+ actions: ['incomingCallConnect'],
173
+ },
174
+ E_RECV_CALL_DISCONNECT: {
175
+ target: 'S_RECV_CALL_DISCONNECT',
176
+ actions: ['incomingCallDisconnect'],
177
+ },
178
+ E_SEND_CALL_DISCONNECT: {
179
+ target: 'S_SEND_CALL_DISCONNECT',
180
+ actions: ['outgoingCallDisconnect'],
181
+ },
182
+ E_RECV_CALL_PROGRESS: {
183
+ target: 'S_RECV_CALL_PROGRESS',
184
+ actions: ['incomingCallProgress'],
185
+ },
186
+ E_UNKNOWN: {
187
+ target: 'S_UNKNOWN',
188
+ actions: ['unknownState'],
189
+ },
190
+ },
191
+ },
192
+ S_SEND_CALL_PROGRESS: {
193
+ on: {
194
+ E_SEND_CALL_CONNECT: {
195
+ target: 'S_SEND_CALL_CONNECT',
196
+ actions: ['outgoingCallConnect'],
197
+ },
198
+ E_RECV_CALL_DISCONNECT: {
199
+ target: 'S_RECV_CALL_DISCONNECT',
200
+ actions: ['incomingCallDisconnect'],
201
+ },
202
+ E_SEND_CALL_DISCONNECT: {
203
+ target: 'S_SEND_CALL_DISCONNECT',
204
+ actions: ['outgoingCallDisconnect'],
205
+ },
206
+ E_UNKNOWN: {
207
+ target: 'S_UNKNOWN',
208
+ actions: ['unknownState'],
209
+ },
210
+ },
211
+ },
212
+ S_RECV_CALL_CONNECT: {
213
+ on: {
214
+ E_CALL_ESTABLISHED: {
215
+ target: 'S_CALL_ESTABLISHED',
216
+ actions: ['callEstablished'],
217
+ },
218
+ E_RECV_CALL_DISCONNECT: {
219
+ target: 'S_RECV_CALL_DISCONNECT',
220
+ actions: ['incomingCallDisconnect'],
221
+ },
222
+ E_SEND_CALL_DISCONNECT: {
223
+ target: 'S_SEND_CALL_DISCONNECT',
224
+ actions: ['outgoingCallDisconnect'],
225
+ },
226
+ E_UNKNOWN: {
227
+ target: 'S_UNKNOWN',
228
+ actions: ['unknownState'],
229
+ },
230
+ },
231
+ },
232
+ S_SEND_CALL_CONNECT: {
233
+ on: {
234
+ E_CALL_ESTABLISHED: {
235
+ target: 'S_CALL_ESTABLISHED',
236
+ actions: ['callEstablished'],
237
+ },
238
+ E_RECV_CALL_DISCONNECT: {
239
+ target: 'S_RECV_CALL_DISCONNECT',
240
+ actions: ['incomingCallDisconnect'],
241
+ },
242
+ E_SEND_CALL_DISCONNECT: {
243
+ target: 'S_SEND_CALL_DISCONNECT',
244
+ actions: ['outgoingCallDisconnect'],
245
+ },
246
+ E_UNKNOWN: {
247
+ target: 'S_UNKNOWN',
248
+ actions: ['unknownState'],
249
+ },
250
+ },
251
+ },
252
+ S_CALL_HOLD: {
253
+ on: {
254
+ E_RECV_CALL_DISCONNECT: {
255
+ target: 'S_RECV_CALL_DISCONNECT',
256
+ actions: ['incomingCallDisconnect'],
257
+ },
258
+ E_SEND_CALL_DISCONNECT: {
259
+ target: 'S_SEND_CALL_DISCONNECT',
260
+ actions: ['outgoingCallDisconnect'],
261
+ },
262
+ E_CALL_ESTABLISHED: {
263
+ target: 'S_CALL_ESTABLISHED',
264
+ actions: ['callEstablished'],
265
+ },
266
+ E_UNKNOWN: {
267
+ target: 'S_UNKNOWN',
268
+ actions: ['unknownState'],
269
+ },
270
+ },
271
+ },
272
+ S_CALL_RESUME: {
273
+ on: {
274
+ E_RECV_CALL_DISCONNECT: {
275
+ target: 'S_RECV_CALL_DISCONNECT',
276
+ actions: ['incomingCallDisconnect'],
277
+ },
278
+ E_SEND_CALL_DISCONNECT: {
279
+ target: 'S_SEND_CALL_DISCONNECT',
280
+ actions: ['outgoingCallDisconnect'],
281
+ },
282
+ E_CALL_ESTABLISHED: {
283
+ target: 'S_CALL_ESTABLISHED',
284
+ actions: ['callEstablished'],
285
+ },
286
+ E_UNKNOWN: {
287
+ target: 'S_UNKNOWN',
288
+ actions: ['unknownState'],
289
+ },
290
+ },
291
+ },
292
+ S_CALL_ESTABLISHED: {
293
+ on: {
294
+ E_CALL_HOLD: {
295
+ target: 'S_CALL_HOLD',
296
+ actions: ['initiateCallHold'],
297
+ },
298
+ E_CALL_RESUME: {
299
+ target: 'S_CALL_RESUME',
300
+ actions: ['initiateCallResume'],
301
+ },
302
+ E_RECV_CALL_DISCONNECT: {
303
+ target: 'S_RECV_CALL_DISCONNECT',
304
+ actions: ['incomingCallDisconnect'],
305
+ },
306
+ E_SEND_CALL_DISCONNECT: {
307
+ target: 'S_SEND_CALL_DISCONNECT',
308
+ actions: ['outgoingCallDisconnect'],
309
+ },
310
+ E_CALL_ESTABLISHED: {
311
+ target: 'S_CALL_ESTABLISHED',
312
+ actions: ['callEstablished'],
313
+ },
314
+ E_UNKNOWN: {
315
+ target: 'S_UNKNOWN',
316
+ actions: ['unknownState'],
317
+ },
318
+ },
319
+ },
320
+ S_RECV_CALL_DISCONNECT: {
321
+ on: {
322
+ E_CALL_CLEARED: 'S_CALL_CLEARED',
323
+ },
324
+ },
325
+ S_SEND_CALL_DISCONNECT: {
326
+ on: {
327
+ E_CALL_CLEARED: 'S_CALL_CLEARED',
328
+ },
329
+ },
330
+ S_UNKNOWN: {
331
+ on: {
332
+ E_CALL_CLEARED: 'S_CALL_CLEARED',
333
+ },
334
+ },
335
+ S_ERROR: {
336
+ on: {
337
+ E_CALL_CLEARED: 'S_CALL_CLEARED',
338
+ },
339
+ },
340
+ S_CALL_CLEARED: {
341
+ type: 'final',
342
+ },
343
+ },
344
+ }, {
345
+ actions: {
346
+ incomingCallSetup: (context, event) => this.handleIncomingCallSetup(event),
347
+ outgoingCallSetup: (context, event) => this.handleOutgoingCallSetup(event),
348
+ incomingCallProgress: (context, event) => this.handleIncomingCallProgress(event),
349
+ outgoingCallAlerting: (context, event) => this.handleOutgoingCallAlerting(event),
350
+ incomingCallConnect: (context, event) => this.handleIncomingCallConnect(event),
351
+ outgoingCallConnect: (context, event) => this.handleOutgoingCallConnect(event),
352
+ callEstablished: (context, event) => this.handleCallEstablished(event),
353
+ initiateCallHold: (context, event) => this.handleCallHold(event),
354
+ initiateCallResume: (context, event) => this.handleCallResume(event),
355
+ incomingCallDisconnect: (context, event) => this.handleIncomingCallDisconnect(event),
356
+ outgoingCallDisconnect: (context, event) => this.handleOutgoingCallDisconnect(event),
357
+ unknownState: (context, event) => this.handleUnknownState(event),
358
+ },
359
+ });
360
+ const mediaMachine = createMachine({
361
+ schema: {
362
+ context: {},
363
+ events: {},
364
+ },
365
+ id: 'roap-state',
366
+ initial: 'S_ROAP_IDLE',
367
+ context: {},
368
+ states: {
369
+ S_ROAP_IDLE: {
370
+ on: {
371
+ E_RECV_ROAP_OFFER_REQUEST: {
372
+ target: 'S_RECV_ROAP_OFFER_REQUEST',
373
+ actions: ['incomingRoapOfferRequest'],
374
+ },
375
+ E_RECV_ROAP_OFFER: {
376
+ target: 'S_RECV_ROAP_OFFER',
377
+ actions: ['incomingRoapOffer'],
378
+ },
379
+ E_SEND_ROAP_OFFER: {
380
+ target: 'S_SEND_ROAP_OFFER',
381
+ actions: ['outgoingRoapOffer'],
382
+ },
383
+ },
384
+ },
385
+ S_RECV_ROAP_OFFER_REQUEST: {
386
+ on: {
387
+ E_SEND_ROAP_OFFER: {
388
+ target: 'S_SEND_ROAP_OFFER',
389
+ actions: ['outgoingRoapOffer'],
390
+ },
391
+ E_ROAP_OK: {
392
+ target: 'S_ROAP_OK',
393
+ actions: ['roapEstablished'],
394
+ },
395
+ E_ROAP_ERROR: {
396
+ target: 'S_ROAP_ERROR',
397
+ actions: ['roapError'],
398
+ },
399
+ },
400
+ },
401
+ S_RECV_ROAP_OFFER: {
402
+ on: {
403
+ E_SEND_ROAP_ANSWER: {
404
+ target: 'S_SEND_ROAP_ANSWER',
405
+ actions: ['outgoingRoapAnswer'],
406
+ },
407
+ E_ROAP_OK: {
408
+ target: 'S_ROAP_OK',
409
+ actions: ['roapEstablished'],
410
+ },
411
+ E_ROAP_ERROR: {
412
+ target: 'S_ROAP_ERROR',
413
+ actions: ['roapError'],
414
+ },
415
+ },
416
+ },
417
+ S_SEND_ROAP_OFFER: {
418
+ on: {
419
+ E_RECV_ROAP_ANSWER: {
420
+ target: 'S_RECV_ROAP_ANSWER',
421
+ actions: ['incomingRoapAnswer'],
422
+ },
423
+ E_SEND_ROAP_ANSWER: {
424
+ target: 'S_SEND_ROAP_ANSWER',
425
+ actions: ['outgoingRoapAnswer'],
426
+ },
427
+ E_SEND_ROAP_OFFER: {
428
+ target: 'S_SEND_ROAP_OFFER',
429
+ actions: ['outgoingRoapOffer'],
430
+ },
431
+ E_ROAP_ERROR: {
432
+ target: 'S_ROAP_ERROR',
433
+ actions: ['roapError'],
434
+ },
435
+ },
436
+ },
437
+ S_RECV_ROAP_ANSWER: {
438
+ on: {
439
+ E_ROAP_OK: {
440
+ target: 'S_ROAP_OK',
441
+ actions: ['roapEstablished'],
442
+ },
443
+ E_ROAP_ERROR: {
444
+ target: 'S_ROAP_ERROR',
445
+ actions: ['roapError'],
446
+ },
447
+ },
448
+ },
449
+ S_SEND_ROAP_ANSWER: {
450
+ on: {
451
+ E_RECV_ROAP_OFFER_REQUEST: {
452
+ target: 'S_RECV_ROAP_OFFER_REQUEST',
453
+ actions: ['incomingRoapOfferRequest'],
454
+ },
455
+ E_RECV_ROAP_OFFER: {
456
+ target: 'S_RECV_ROAP_OFFER',
457
+ actions: ['incomingRoapOffer'],
458
+ },
459
+ E_ROAP_OK: {
460
+ target: 'S_ROAP_OK',
461
+ actions: ['roapEstablished'],
462
+ },
463
+ E_SEND_ROAP_ANSWER: {
464
+ target: 'S_SEND_ROAP_ANSWER',
465
+ actions: ['outgoingRoapAnswer'],
466
+ },
467
+ E_ROAP_ERROR: {
468
+ target: 'S_ROAP_ERROR',
469
+ actions: ['roapError'],
470
+ },
471
+ },
472
+ },
473
+ S_ROAP_OK: {
474
+ on: {
475
+ E_RECV_ROAP_OFFER_REQUEST: {
476
+ target: 'S_RECV_ROAP_OFFER_REQUEST',
477
+ actions: ['incomingRoapOfferRequest'],
478
+ },
479
+ E_RECV_ROAP_OFFER: {
480
+ target: 'S_RECV_ROAP_OFFER',
481
+ actions: ['incomingRoapOffer'],
482
+ },
483
+ E_ROAP_OK: {
484
+ target: 'S_ROAP_OK',
485
+ actions: ['roapEstablished'],
486
+ },
487
+ E_SEND_ROAP_OFFER: {
488
+ target: 'S_SEND_ROAP_OFFER',
489
+ actions: ['outgoingRoapOffer'],
490
+ },
491
+ E_ROAP_ERROR: {
492
+ target: 'S_ROAP_ERROR',
493
+ actions: ['roapError'],
494
+ },
495
+ E_ROAP_TEARDOWN: {
496
+ target: 'S_ROAP_TEARDOWN',
497
+ },
498
+ },
499
+ },
500
+ S_ROAP_ERROR: {
501
+ on: {
502
+ E_ROAP_TEARDOWN: {
503
+ target: 'S_ROAP_TEARDOWN',
504
+ },
505
+ E_RECV_ROAP_OFFER_REQUEST: {
506
+ target: 'S_RECV_ROAP_OFFER_REQUEST',
507
+ actions: ['incomingRoapOfferRequest'],
508
+ },
509
+ E_RECV_ROAP_OFFER: {
510
+ target: 'S_RECV_ROAP_OFFER',
511
+ actions: ['incomingRoapOffer'],
512
+ },
513
+ E_RECV_ROAP_ANSWER: {
514
+ target: 'S_RECV_ROAP_ANSWER',
515
+ actions: ['incomingRoapAnswer'],
516
+ },
517
+ E_ROAP_OK: {
518
+ target: 'S_ROAP_OK',
519
+ actions: ['roapEstablished'],
520
+ },
521
+ },
522
+ },
523
+ S_ROAP_TEARDOWN: {
524
+ type: 'final',
525
+ },
526
+ },
527
+ }, {
528
+ actions: {
529
+ incomingRoapOffer: (context, event) => this.handleIncomingRoapOffer(context, event),
530
+ incomingRoapAnswer: (context, event) => this.handleIncomingRoapAnswer(context, event),
531
+ incomingRoapOfferRequest: (context, event) => this.handleIncomingRoapOfferRequest(context, event),
532
+ outgoingRoapOffer: (context, event) => this.handleOutgoingRoapOffer(context, event),
533
+ outgoingRoapAnswer: (context, event) => this.handleOutgoingRoapAnswer(context, event),
534
+ roapEstablished: (context, event) => this.handleRoapEstablished(context, event),
535
+ roapError: (context, event) => this.handleRoapError(context, event),
536
+ },
537
+ });
538
+ this.callStateMachine = interpret(callMachine)
539
+ .onTransition((state, event) => {
540
+ log.log(`Call StateMachine:- state=${state.value}, event=${JSON.stringify(event.type)}`, {});
541
+ if (state.value !== 'S_UNKNOWN') {
542
+ this.metricManager.submitCallMetric(METRIC_EVENT.CALL, state.value.toString(), METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, undefined);
543
+ }
544
+ })
545
+ .start();
546
+ this.mediaStateMachine = interpret(mediaMachine)
547
+ .onTransition((state, event) => {
548
+ log.log(`Media StateMachine:- state=${state.value}, event=${JSON.stringify(event.type)}`, {});
549
+ if (state.value !== 'S_ROAP_ERROR') {
550
+ this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA, state.value.toString(), METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, this.localRoapMessage.sdp, this.remoteRoapMessage?.sdp, undefined);
551
+ }
552
+ })
553
+ .start();
554
+ this.muted = false;
555
+ }
556
+ handleIncomingCallSetup(event) {
557
+ log.info(`handleIncomingCallSetup: ${this.getCorrelationId()} `, {
558
+ file: CALL_FILE,
559
+ method: this.handleIncomingCallSetup.name,
560
+ });
561
+ this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_ALERTING' });
562
+ }
563
+ async handleOutgoingCallSetup(event) {
564
+ log.info(`handleOutgoingCallSetup: ${this.getCorrelationId()} `, {
565
+ file: CALL_FILE,
566
+ method: this.handleOutgoingCallSetup.name,
567
+ });
568
+ const message = event.data;
569
+ try {
570
+ const response = await this.post(message);
571
+ log.log(`handleOutgoingCallSetup: Response code: ${response.statusCode}`, {
572
+ file: CALL_FILE,
573
+ method: this.handleOutgoingCallSetup.name,
574
+ });
575
+ this.setCallId(response.body.callId);
576
+ }
577
+ catch (e) {
578
+ log.warn('Call setup failed with Mobius', {
579
+ file: CALL_FILE,
580
+ method: this.handleOutgoingCallSetup.name,
581
+ });
582
+ const errData = e;
583
+ handleCallErrors((error) => {
584
+ this.emit(EVENT_KEYS.CALL_ERROR, error);
585
+ this.submitCallErrorMetric(error);
586
+ this.sendCallStateMachineEvt({ type: 'E_UNKNOWN', data: errData });
587
+ }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, this.handleOutgoingCallSetup.name, CALL_FILE);
588
+ }
589
+ }
590
+ async handleCallHold(event) {
591
+ log.info(`handleCallHold: ${this.getCorrelationId()} `, {
592
+ file: CALL_FILE,
593
+ method: this.handleCallHold.name,
594
+ });
595
+ try {
596
+ const response = await this.postSSRequest(undefined, SUPPLEMENTARY_SERVICES.HOLD);
597
+ log.log(`Response code: ${response.statusCode}`, {
598
+ file: CALL_FILE,
599
+ method: this.handleCallHold.name,
600
+ });
601
+ if (this.isHeld() === false) {
602
+ this.supplementaryServicesTimer = setTimeout(async () => {
603
+ const errorContext = { file: CALL_FILE, method: this.handleCallHold.name };
604
+ log.warn('Hold response timed out', {
605
+ file: CALL_FILE,
606
+ method: this.handleCallHold.name,
607
+ });
608
+ const callError = createCallError('An error occurred while placing the call on hold. Wait a moment and try again.', errorContext, ERROR_TYPE.TIMEOUT, this.getCorrelationId(), ERROR_LAYER.CALL_CONTROL);
609
+ this.emit(EVENT_KEYS.HOLD_ERROR, callError);
610
+ this.submitCallErrorMetric(callError);
611
+ }, SUPPLEMENTARY_SERVICES_TIMEOUT);
612
+ }
613
+ }
614
+ catch (e) {
615
+ log.warn('Call Hold failed with Mobius', {
616
+ file: CALL_FILE,
617
+ method: this.handleCallHold.name,
618
+ });
619
+ const errData = e;
620
+ handleCallErrors((error) => {
621
+ this.emit(EVENT_KEYS.HOLD_ERROR, error);
622
+ this.submitCallErrorMetric(error);
623
+ this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED', data: errData });
624
+ }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, this.handleOutgoingCallSetup.name, CALL_FILE);
625
+ }
626
+ }
627
+ async handleCallResume(event) {
628
+ log.info(`handleCallResume: ${this.getCorrelationId()} `, {
629
+ file: CALL_FILE,
630
+ method: this.handleCallResume.name,
631
+ });
632
+ try {
633
+ const response = await this.postSSRequest(undefined, SUPPLEMENTARY_SERVICES.RESUME);
634
+ log.log(`Response code: ${response.statusCode}`, {
635
+ file: CALL_FILE,
636
+ method: this.handleCallResume.name,
637
+ });
638
+ if (this.isHeld() === true) {
639
+ this.supplementaryServicesTimer = setTimeout(async () => {
640
+ const errorContext = { file: CALL_FILE, method: this.handleCallResume.name };
641
+ log.warn('Resume response timed out', {
642
+ file: CALL_FILE,
643
+ method: this.handleCallResume.name,
644
+ });
645
+ const callError = createCallError('An error occurred while resuming the call. Wait a moment and try again.', errorContext, ERROR_TYPE.TIMEOUT, this.getCorrelationId(), ERROR_LAYER.CALL_CONTROL);
646
+ this.emit(EVENT_KEYS.RESUME_ERROR, callError);
647
+ this.submitCallErrorMetric(callError);
648
+ }, SUPPLEMENTARY_SERVICES_TIMEOUT);
649
+ }
650
+ }
651
+ catch (e) {
652
+ log.warn('Call Resume failed with Mobius', {
653
+ file: CALL_FILE,
654
+ method: this.handleCallResume.name,
655
+ });
656
+ const errData = e;
657
+ handleCallErrors((error) => {
658
+ this.emit(EVENT_KEYS.RESUME_ERROR, error);
659
+ this.submitCallErrorMetric(error);
660
+ this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED', data: errData });
661
+ }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, this.handleOutgoingCallSetup.name, CALL_FILE);
662
+ }
663
+ }
664
+ handleIncomingCallProgress(event) {
665
+ log.info(`handleIncomingCallProgress: ${this.getCorrelationId()} `, {
666
+ file: CALL_FILE,
667
+ method: this.handleIncomingCallProgress.name,
668
+ });
669
+ const data = event.data;
670
+ if (data?.callProgressData?.inbandMedia) {
671
+ log.log('Inband media present. Setting Early Media flag', {
672
+ file: CALL_FILE,
673
+ method: this.handleIncomingCallProgress.name,
674
+ });
675
+ this.earlyMedia = true;
676
+ }
677
+ else {
678
+ log.log('Inband media not present.', {
679
+ file: CALL_FILE,
680
+ method: this.handleIncomingCallProgress.name,
681
+ });
682
+ }
683
+ if (data?.callerId) {
684
+ log.info('Processing Caller-Id data', {
685
+ file: CALL_FILE,
686
+ method: this.handleIncomingCallProgress.name,
687
+ });
688
+ this.startCallerIdResolution(data.callerId);
689
+ }
690
+ this.emit(EVENT_KEYS.PROGRESS, this.correlationId);
691
+ }
692
+ handleIncomingRoapOfferRequest(context, event) {
693
+ log.info(`handleIncomingRoapOfferRequest: ${this.getCorrelationId()} `, {
694
+ file: CALL_FILE,
695
+ method: this.handleIncomingRoapOfferRequest.name,
696
+ });
697
+ const message = event.data;
698
+ if (!this.mediaConnection) {
699
+ log.info('Media connection is not up, buffer the remote Offer Request for later handling', {
700
+ file: CALL_FILE,
701
+ method: this.handleIncomingRoapOfferRequest.name,
702
+ });
703
+ this.seq = message.seq;
704
+ log.info(`Setting Sequence No: ${this.seq}`, {
705
+ file: CALL_FILE,
706
+ method: this.handleIncomingRoapOfferRequest.name,
707
+ });
708
+ this.remoteRoapMessage = message;
709
+ }
710
+ else if (this.receivedRoapOKSeq === message.seq - 2) {
711
+ log.info('Waiting for Roap OK, buffer the remote Offer Request for later handling', {
712
+ file: CALL_FILE,
713
+ method: this.handleIncomingRoapOfferRequest.name,
714
+ });
715
+ this.remoteRoapMessage = message;
716
+ }
717
+ else {
718
+ message.seq = this.seq + 1;
719
+ this.seq = message.seq;
720
+ this.mediaConnection.roapMessageReceived(message);
721
+ }
722
+ }
723
+ async handleOutgoingCallAlerting(event) {
724
+ log.info(`handleOutgoingCallAlerting: ${this.getCorrelationId()} `, {
725
+ file: CALL_FILE,
726
+ method: this.handleOutgoingCallAlerting.name,
727
+ });
728
+ try {
729
+ const res = await this.patch(MobiusCallState.ALERTING);
730
+ log.log(`PATCH response: ${res.statusCode}`, {
731
+ file: CALL_FILE,
732
+ method: this.handleOutgoingCallAlerting.name,
733
+ });
734
+ }
735
+ catch (err) {
736
+ log.warn('Call Progress failed with Mobius', {
737
+ file: CALL_FILE,
738
+ method: this.handleOutgoingCallAlerting.name,
739
+ });
740
+ const errData = err;
741
+ handleCallErrors((error) => {
742
+ this.emit(EVENT_KEYS.CALL_ERROR, error);
743
+ this.submitCallErrorMetric(error);
744
+ this.sendCallStateMachineEvt({ type: 'E_UNKNOWN', data: errData });
745
+ }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, this.handleOutgoingCallAlerting.name, CALL_FILE);
746
+ }
747
+ }
748
+ handleIncomingCallConnect(event) {
749
+ log.info(`handleIncomingCallConnect: ${this.getCorrelationId()} `, {
750
+ file: CALL_FILE,
751
+ method: this.handleIncomingCallConnect.name,
752
+ });
753
+ this.emit(EVENT_KEYS.CONNECT, this.correlationId);
754
+ if (this.earlyMedia || this.mediaNegotiationCompleted) {
755
+ this.mediaNegotiationCompleted = false;
756
+ this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED' });
757
+ }
758
+ }
759
+ async handleOutgoingCallConnect(event) {
760
+ log.info(`handleOutgoingCallConnect: ${this.getCorrelationId()} `, {
761
+ file: CALL_FILE,
762
+ method: this.handleOutgoingCallConnect.name,
763
+ });
764
+ if (!this.remoteRoapMessage) {
765
+ log.warn('Offer not yet received from remote end... Exiting', {
766
+ file: CALL_FILE,
767
+ method: this.handleOutgoingCallConnect.name,
768
+ });
769
+ return;
770
+ }
771
+ try {
772
+ const res = await this.patch(MobiusCallState.CONNECTED);
773
+ log.log(`PATCH response: ${res.statusCode}`, {
774
+ file: CALL_FILE,
775
+ method: this.handleOutgoingCallConnect.name,
776
+ });
777
+ this.mediaConnection.roapMessageReceived(this.remoteRoapMessage);
778
+ }
779
+ catch (err) {
780
+ log.warn('Call Connect failed with Mobius', {
781
+ file: CALL_FILE,
782
+ method: this.handleOutgoingCallConnect.name,
783
+ });
784
+ const errData = err;
785
+ handleCallErrors((error) => {
786
+ this.emit(EVENT_KEYS.CALL_ERROR, error);
787
+ this.submitCallErrorMetric(error);
788
+ this.sendCallStateMachineEvt({ type: 'E_UNKNOWN', data: errData });
789
+ }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, this.handleOutgoingCallConnect.name, CALL_FILE);
790
+ }
791
+ }
792
+ async handleIncomingCallDisconnect(event) {
793
+ log.info(`handleIncomingCallDisconnect: ${this.getCorrelationId()} `, {
794
+ file: CALL_FILE,
795
+ method: this.handleIncomingCallDisconnect.name,
796
+ });
797
+ this.setDisconnectReason();
798
+ try {
799
+ const response = await this.delete();
800
+ log.log(`handleOutgoingCallDisconnect: Response code: ${response.statusCode}`, {
801
+ file: CALL_FILE,
802
+ method: this.handleIncomingCallDisconnect.name,
803
+ });
804
+ }
805
+ catch (e) {
806
+ log.warn('Delete Call failed with Mobius', {
807
+ file: CALL_FILE,
808
+ method: this.handleIncomingCallDisconnect.name,
809
+ });
810
+ }
811
+ this.deleteCb(this.correlationId);
812
+ if (this.sessionTimer) {
813
+ clearInterval(this.sessionTimer);
814
+ }
815
+ if (this.mediaConnection) {
816
+ this.mediaConnection.close();
817
+ log.info('Closing media channel', { file: CALL_FILE, method: 'handleIncomingCallDisconnect' });
818
+ }
819
+ this.sendMediaStateMachineEvt({ type: 'E_ROAP_TEARDOWN' });
820
+ this.sendCallStateMachineEvt({ type: 'E_CALL_CLEARED' });
821
+ this.emit(EVENT_KEYS.DISCONNECT, this.correlationId);
822
+ }
823
+ async handleOutgoingCallDisconnect(event) {
824
+ this.setDisconnectReason();
825
+ try {
826
+ const response = await this.delete();
827
+ log.log(`handleOutgoingCallDisconnect: Response code: ${response.statusCode}`, {
828
+ file: CALL_FILE,
829
+ method: this.handleOutgoingCallDisconnect.name,
830
+ });
831
+ }
832
+ catch (e) {
833
+ log.warn('Delete Call failed with Mobius', {
834
+ file: CALL_FILE,
835
+ method: this.handleOutgoingCallDisconnect.name,
836
+ });
837
+ }
838
+ this.deleteCb(this.correlationId);
839
+ if (this.sessionTimer) {
840
+ clearInterval(this.sessionTimer);
841
+ }
842
+ if (this.mediaConnection) {
843
+ this.mediaConnection.close();
844
+ log.info('Closing media channel', { file: CALL_FILE, method: 'handleOutgoingCallDisconnect' });
845
+ }
846
+ this.sendMediaStateMachineEvt({ type: 'E_ROAP_TEARDOWN' });
847
+ this.sendCallStateMachineEvt({ type: 'E_CALL_CLEARED' });
848
+ }
849
+ handleCallEstablished(event) {
850
+ log.info(`handleCallEstablished: ${this.getCorrelationId()} `, {
851
+ file: CALL_FILE,
852
+ method: this.handleCallEstablished.name,
853
+ });
854
+ this.emit(EVENT_KEYS.ESTABLISHED, this.correlationId);
855
+ this.earlyMedia = false;
856
+ this.connected = true;
857
+ if (this.sessionTimer) {
858
+ log.log('Resetting session timer', {
859
+ file: CALL_FILE,
860
+ method: 'handleCallEstablished',
861
+ });
862
+ clearInterval(this.sessionTimer);
863
+ }
864
+ this.sessionTimer = setInterval(async () => {
865
+ try {
866
+ const res = await this.postStatus();
867
+ log.info(`Session refresh successful`, {
868
+ file: CALL_FILE,
869
+ method: 'handleCallEstablished',
870
+ });
871
+ }
872
+ catch (err) {
873
+ const error = err;
874
+ if (this.sessionTimer) {
875
+ clearInterval(this.sessionTimer);
876
+ }
877
+ handleCallErrors((callError) => {
878
+ this.emit(EVENT_KEYS.CALL_ERROR, callError);
879
+ this.submitCallErrorMetric(callError);
880
+ }, ERROR_LAYER.CALL_CONTROL, (interval) => {
881
+ setTimeout(() => {
882
+ this.postStatus();
883
+ this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED' });
884
+ }, interval * 1000);
885
+ }, this.getCorrelationId(), error, this.handleCallEstablished.name, CALL_FILE);
886
+ }
887
+ }, DEFAULT_SESSION_TIMER);
888
+ }
889
+ async handleUnknownState(event) {
890
+ log.info(`handleUnknownState: ${this.getCorrelationId()} `, {
891
+ file: CALL_FILE,
892
+ method: this.handleUnknownState.name,
893
+ });
894
+ const eventData = event.data;
895
+ if (!eventData?.media) {
896
+ log.warn('Call failed due to signalling issue', {
897
+ file: CALL_FILE,
898
+ method: this.handleUnknownState.name,
899
+ });
900
+ }
901
+ try {
902
+ this.setDisconnectReason();
903
+ const response = await this.delete();
904
+ log.log(`handleOutgoingCallDisconnect: Response code: ${response.statusCode}`, {
905
+ file: CALL_FILE,
906
+ method: this.handleUnknownState.name,
907
+ });
908
+ }
909
+ catch (e) {
910
+ log.warn('Delete Call failed with Mobius', {
911
+ file: CALL_FILE,
912
+ method: this.handleUnknownState.name,
913
+ });
914
+ }
915
+ this.deleteCb(this.correlationId);
916
+ if (this.sessionTimer) {
917
+ clearInterval(this.sessionTimer);
918
+ }
919
+ if (this.mediaConnection) {
920
+ this.mediaConnection.close();
921
+ log.info('Closing media channel', {
922
+ file: CALL_FILE,
923
+ method: this.handleUnknownState.name,
924
+ });
925
+ }
926
+ this.sendMediaStateMachineEvt({ type: 'E_ROAP_TEARDOWN' });
927
+ this.sendCallStateMachineEvt({ type: 'E_CALL_CLEARED' });
928
+ }
929
+ getEmitterCallback(errData) {
930
+ return (error) => {
931
+ switch (this.callStateMachine.state.value) {
932
+ case 'S_CALL_HOLD':
933
+ this.emit(EVENT_KEYS.HOLD_ERROR, error);
934
+ if (this.supplementaryServicesTimer) {
935
+ clearTimeout(this.supplementaryServicesTimer);
936
+ this.supplementaryServicesTimer = undefined;
937
+ }
938
+ this.submitCallErrorMetric(error);
939
+ this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED', data: errData });
940
+ return;
941
+ case 'S_CALL_RESUME':
942
+ this.emit(EVENT_KEYS.RESUME_ERROR, error);
943
+ this.submitCallErrorMetric(error);
944
+ this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED', data: errData });
945
+ return;
946
+ default:
947
+ this.emit(EVENT_KEYS.CALL_ERROR, error);
948
+ this.submitCallErrorMetric(error);
949
+ if (!this.connected) {
950
+ this.sendMediaStateMachineEvt({ type: 'E_ROAP_ERROR', data: errData });
951
+ }
952
+ }
953
+ };
954
+ }
955
+ async handleRoapEstablished(context, event) {
956
+ log.info(`handleRoapEstablished: ${this.getCorrelationId()} `, {
957
+ file: CALL_FILE,
958
+ method: 'handleRoapEstablished',
959
+ });
960
+ const { received, message } = event.data;
961
+ this.receivedRoapOKSeq = message.seq;
962
+ if (!received) {
963
+ log.info('Sending Media Ok to the remote End', {
964
+ file: CALL_FILE,
965
+ method: 'handleRoapEstablished',
966
+ });
967
+ try {
968
+ if (this.callStateMachine.state.value === 'S_RECV_CALL_PROGRESS') {
969
+ log.info('Media negotiation completed before call connect. Setting media negotiation completed flag.', {
970
+ file: CALL_FILE,
971
+ method: 'handleRoapEstablished',
972
+ });
973
+ this.mediaNegotiationCompleted = true;
974
+ }
975
+ message.seq = this.seq;
976
+ const res = await this.postMedia(message);
977
+ log.log(`handleRoapEstablished: Response code: ${res.statusCode}`, {
978
+ file: CALL_FILE,
979
+ method: 'handleRoapEstablished',
980
+ });
981
+ if (!this.earlyMedia && !this.mediaNegotiationCompleted) {
982
+ this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED' });
983
+ }
984
+ }
985
+ catch (err) {
986
+ log.warn('MediaOk failed with Mobius', {
987
+ file: CALL_FILE,
988
+ method: 'handleRoapEstablished',
989
+ });
990
+ const errData = err;
991
+ handleCallErrors(this.getEmitterCallback(errData), ERROR_LAYER.MEDIA, (interval) => {
992
+ if (this.connected) {
993
+ setTimeout(() => {
994
+ this.sendMediaStateMachineEvt({ type: 'E_ROAP_OK', data: event.data });
995
+ }, interval * 1000);
996
+ }
997
+ }, this.getCorrelationId(), errData, this.handleRoapEstablished.name, CALL_FILE);
998
+ }
999
+ }
1000
+ else {
1001
+ log.info('Notifying internal-media-core about ROAP OK message', {
1002
+ file: CALL_FILE,
1003
+ method: 'handleRoapEstablished',
1004
+ });
1005
+ message.seq = this.seq;
1006
+ if (this.mediaConnection) {
1007
+ this.mediaConnection.roapMessageReceived(message);
1008
+ }
1009
+ if (!this.earlyMedia) {
1010
+ this.sendCallStateMachineEvt({ type: 'E_CALL_ESTABLISHED' });
1011
+ }
1012
+ if (this.remoteRoapMessage && this.remoteRoapMessage.seq > this.seq) {
1013
+ if (this.remoteRoapMessage.messageType === 'OFFER_REQUEST') {
1014
+ this.sendMediaStateMachineEvt({
1015
+ type: 'E_RECV_ROAP_OFFER_REQUEST',
1016
+ data: this.remoteRoapMessage,
1017
+ });
1018
+ }
1019
+ else if (this.remoteRoapMessage.messageType === 'OFFER') {
1020
+ this.sendMediaStateMachineEvt({ type: 'E_RECV_ROAP_OFFER', data: this.remoteRoapMessage });
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ async handleRoapError(context, event) {
1026
+ log.info(`handleRoapError: ${this.getCorrelationId()}`, {
1027
+ file: CALL_FILE,
1028
+ method: this.handleRoapError.name,
1029
+ });
1030
+ const message = event.data;
1031
+ if (message) {
1032
+ try {
1033
+ const res = await this.postMedia(message);
1034
+ log.info(`Response code: ${res.statusCode}`, {
1035
+ file: CALL_FILE,
1036
+ method: this.handleRoapError.name,
1037
+ });
1038
+ }
1039
+ catch (err) {
1040
+ log.warn('Failed to communicate ROAP error with Mobius', {
1041
+ file: CALL_FILE,
1042
+ method: this.handleRoapError.name,
1043
+ });
1044
+ const errData = err;
1045
+ handleCallErrors((error) => {
1046
+ this.emit(EVENT_KEYS.CALL_ERROR, error);
1047
+ this.submitCallErrorMetric(error);
1048
+ }, ERROR_LAYER.MEDIA, (interval) => undefined, this.getCorrelationId(), errData, this.handleRoapError.name, CALL_FILE);
1049
+ }
1050
+ }
1051
+ if (!this.connected) {
1052
+ log.warn('Call failed due to media issue', {
1053
+ file: CALL_FILE,
1054
+ method: 'handleRoapError',
1055
+ });
1056
+ this.sendCallStateMachineEvt({ type: 'E_UNKNOWN', data: { media: true } });
1057
+ }
1058
+ }
1059
+ async handleOutgoingRoapOffer(context, event) {
1060
+ log.info(`handleOutgoingRoapOffer: ${this.getCorrelationId()}`, {
1061
+ file: CALL_FILE,
1062
+ method: this.handleOutgoingRoapOffer.name,
1063
+ });
1064
+ const message = event.data;
1065
+ if (!message?.sdp) {
1066
+ log.info('Initializing Offer...', {
1067
+ file: CALL_FILE,
1068
+ method: this.handleOutgoingRoapOffer.name,
1069
+ });
1070
+ this.mediaConnection.initiateOffer();
1071
+ return;
1072
+ }
1073
+ try {
1074
+ const res = await this.postMedia(message);
1075
+ log.log(`handleOutgoingRoapOffer: Response code: ${res.statusCode}`, {
1076
+ file: CALL_FILE,
1077
+ method: this.handleOutgoingRoapOffer.name,
1078
+ });
1079
+ }
1080
+ catch (err) {
1081
+ log.warn('MediaOk failed with Mobius', {
1082
+ file: CALL_FILE,
1083
+ method: this.handleOutgoingRoapOffer.name,
1084
+ });
1085
+ const errData = err;
1086
+ handleCallErrors(this.getEmitterCallback(errData), ERROR_LAYER.MEDIA, (interval) => {
1087
+ if (this.connected) {
1088
+ setTimeout(() => {
1089
+ this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_OFFER', data: event.data });
1090
+ }, interval * 1000);
1091
+ }
1092
+ }, this.getCorrelationId(), errData, this.handleOutgoingRoapOffer.name, CALL_FILE);
1093
+ }
1094
+ }
1095
+ async handleOutgoingRoapAnswer(context, event) {
1096
+ log.info(`handleOutgoingRoapAnswer: ${this.getCorrelationId()}`, {
1097
+ file: CALL_FILE,
1098
+ method: this.handleOutgoingRoapAnswer.name,
1099
+ });
1100
+ const message = event.data;
1101
+ try {
1102
+ message.seq = this.seq;
1103
+ const res = await this.postMedia(message);
1104
+ log.log(`handleOutgoingRoapAnswer: Response code: ${res.statusCode}`, {
1105
+ file: CALL_FILE,
1106
+ method: this.handleOutgoingRoapAnswer.name,
1107
+ });
1108
+ }
1109
+ catch (err) {
1110
+ log.warn('MediaAnswer failed with Mobius', {
1111
+ file: CALL_FILE,
1112
+ method: this.handleOutgoingRoapAnswer.name,
1113
+ });
1114
+ const errData = err;
1115
+ handleCallErrors(this.getEmitterCallback(errData), ERROR_LAYER.MEDIA, (interval) => {
1116
+ if (this.connected) {
1117
+ setTimeout(() => {
1118
+ this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_ANSWER', data: event.data });
1119
+ }, interval * 1000);
1120
+ }
1121
+ }, this.getCorrelationId(), errData, this.handleOutgoingRoapAnswer.name, CALL_FILE);
1122
+ }
1123
+ }
1124
+ handleIncomingRoapOffer(context, event) {
1125
+ log.info(`handleIncomingRoapOffer: ${this.getCorrelationId()}`, {
1126
+ file: CALL_FILE,
1127
+ method: this.handleIncomingRoapOffer.name,
1128
+ });
1129
+ const message = event.data;
1130
+ this.remoteRoapMessage = message;
1131
+ if (!this.mediaConnection) {
1132
+ log.info('Media connection is not up, buffer the remote offer for later handling', {
1133
+ file: CALL_FILE,
1134
+ method: this.handleIncomingRoapOffer.name,
1135
+ });
1136
+ this.seq = message.seq;
1137
+ log.info(`Setting Sequence No: ${this.seq}`, {
1138
+ file: CALL_FILE,
1139
+ method: this.handleIncomingRoapOffer.name,
1140
+ });
1141
+ }
1142
+ else if (this.receivedRoapOKSeq === message.seq - 2) {
1143
+ log.info('Waiting for Roap OK, buffer the remote offer for later handling', {
1144
+ file: CALL_FILE,
1145
+ method: this.handleIncomingRoapOffer.name,
1146
+ });
1147
+ this.remoteRoapMessage = message;
1148
+ }
1149
+ else {
1150
+ log.info('Handling new offer...', {
1151
+ file: CALL_FILE,
1152
+ method: this.handleIncomingRoapOffer.name,
1153
+ });
1154
+ this.seq = message.seq;
1155
+ if (this.mediaConnection) {
1156
+ this.mediaConnection.roapMessageReceived(message);
1157
+ }
1158
+ }
1159
+ }
1160
+ handleIncomingRoapAnswer(context, event) {
1161
+ log.info(`handleIncomingRoapAnswer: ${this.getCorrelationId()}`, {
1162
+ file: CALL_FILE,
1163
+ method: this.handleIncomingRoapAnswer.name,
1164
+ });
1165
+ const message = event.data;
1166
+ this.remoteRoapMessage = message;
1167
+ message.seq = this.seq;
1168
+ if (this.mediaConnection) {
1169
+ this.mediaConnection.roapMessageReceived(message);
1170
+ }
1171
+ }
1172
+ initMediaConnection(localAudioTrack, debugId) {
1173
+ const mediaConnection = new RoapMediaConnection({
1174
+ skipInactiveTransceivers: true,
1175
+ iceServers: [],
1176
+ sdpMunging: {
1177
+ convertPort9to0: true,
1178
+ addContentSlides: false,
1179
+ },
1180
+ }, {
1181
+ localTracks: { audio: localAudioTrack },
1182
+ direction: {
1183
+ audio: 'sendrecv',
1184
+ video: 'inactive',
1185
+ screenShareVideo: 'inactive',
1186
+ },
1187
+ }, debugId || `WebexCallSDK-${this.correlationId}`);
1188
+ this.mediaConnection = mediaConnection;
1189
+ }
1190
+ getDirection = () => this.direction;
1191
+ getCallId = () => this.callId;
1192
+ getCorrelationId = () => this.correlationId;
1193
+ sendCallStateMachineEvt(event) {
1194
+ this.callStateMachine.send(event);
1195
+ }
1196
+ sendMediaStateMachineEvt(event) {
1197
+ this.mediaStateMachine.send(event);
1198
+ }
1199
+ setCallId = (callId) => {
1200
+ this.callId = callId;
1201
+ log.info(`Setting callId : ${this.callId} for correlationId: ${this.correlationId}`, {
1202
+ file: CALL_FILE,
1203
+ method: this.setCallId.name,
1204
+ });
1205
+ };
1206
+ setDisconnectReason() {
1207
+ if (this.connected || this.direction === CallDirection.OUTBOUND) {
1208
+ this.disconnectReason.code = DisconnectCode.NORMAL;
1209
+ this.disconnectReason.cause = DisconnectCause.NORMAL;
1210
+ }
1211
+ else {
1212
+ this.disconnectReason.code = DisconnectCode.BUSY;
1213
+ this.disconnectReason.cause = DisconnectCause.BUSY;
1214
+ }
1215
+ }
1216
+ getDisconnectReason = () => {
1217
+ return this.disconnectReason;
1218
+ };
1219
+ async answer(localAudioStream) {
1220
+ const localAudioTrack = localAudioStream.outputStream.getAudioTracks()[0];
1221
+ localAudioTrack.enabled = true;
1222
+ if (!this.mediaConnection) {
1223
+ this.initMediaConnection(localAudioTrack);
1224
+ this.mediaRoapEventsListener();
1225
+ this.mediaTrackListener();
1226
+ }
1227
+ if (this.callStateMachine.state.value === 'S_SEND_CALL_PROGRESS') {
1228
+ this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_CONNECT' });
1229
+ }
1230
+ else {
1231
+ log.warn(`Call cannot be answered because the state is : ${this.callStateMachine.state.value}`, { file: CALL_FILE, method: 'answer' });
1232
+ }
1233
+ }
1234
+ async dial(localAudioStream) {
1235
+ const localAudioTrack = localAudioStream.outputStream.getAudioTracks()[0];
1236
+ localAudioTrack.enabled = true;
1237
+ if (!this.mediaConnection) {
1238
+ this.initMediaConnection(localAudioTrack);
1239
+ this.mediaRoapEventsListener();
1240
+ this.mediaTrackListener();
1241
+ }
1242
+ if (this.mediaStateMachine.state.value === 'S_ROAP_IDLE') {
1243
+ this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_OFFER' });
1244
+ }
1245
+ else {
1246
+ log.warn(`Call cannot be dialed because the state is already : ${this.mediaStateMachine.state.value}`, { file: CALL_FILE, method: 'dial' });
1247
+ }
1248
+ }
1249
+ post = async (roapMessage) => {
1250
+ return this.webex.request({
1251
+ uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALL_ENDPOINT_RESOURCE}`,
1252
+ method: HTTP_METHODS.POST,
1253
+ service: ALLOWED_SERVICES.MOBIUS,
1254
+ headers: {
1255
+ [CISCO_DEVICE_URL]: this.webex.internal.device.url,
1256
+ [SPARK_USER_AGENT]: CALLING_USER_AGENT,
1257
+ },
1258
+ body: {
1259
+ device: {
1260
+ deviceId: this.deviceId,
1261
+ correlationId: this.correlationId,
1262
+ },
1263
+ callee: {
1264
+ type: this.destination.type,
1265
+ address: this.destination.address,
1266
+ },
1267
+ localMedia: {
1268
+ roap: roapMessage,
1269
+ mediaId: uuid(),
1270
+ },
1271
+ },
1272
+ });
1273
+ };
1274
+ async patch(state) {
1275
+ log.info(`Send a PATCH for ${state} to mobius`, {
1276
+ file: CALL_FILE,
1277
+ method: this.patch.name,
1278
+ });
1279
+ return this.webex.request({
1280
+ uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALLS_ENDPOINT_RESOURCE}/${this.callId}`,
1281
+ method: HTTP_METHODS.PATCH,
1282
+ service: ALLOWED_SERVICES.MOBIUS,
1283
+ headers: {
1284
+ [CISCO_DEVICE_URL]: this.webex.internal.device.url,
1285
+ [SPARK_USER_AGENT]: CALLING_USER_AGENT,
1286
+ },
1287
+ body: {
1288
+ device: {
1289
+ deviceId: this.deviceId,
1290
+ correlationId: this.correlationId,
1291
+ },
1292
+ callId: this.callId,
1293
+ callState: state,
1294
+ inbandMedia: false,
1295
+ },
1296
+ });
1297
+ }
1298
+ async postSSRequest(context, type) {
1299
+ const request = {
1300
+ uri: `${this.mobiusUrl}${SERVICES_ENDPOINT}`,
1301
+ method: HTTP_METHODS.POST,
1302
+ service: ALLOWED_SERVICES.MOBIUS,
1303
+ headers: {
1304
+ [CISCO_DEVICE_URL]: this.webex.internal.device.url,
1305
+ [SPARK_USER_AGENT]: CALLING_USER_AGENT,
1306
+ },
1307
+ body: {
1308
+ device: {
1309
+ deviceId: this.deviceId,
1310
+ correlationId: this.correlationId,
1311
+ },
1312
+ callId: this.callId,
1313
+ },
1314
+ };
1315
+ switch (type) {
1316
+ case SUPPLEMENTARY_SERVICES.HOLD: {
1317
+ request.uri = `${request.uri}/${CALL_HOLD_SERVICE}/${HOLD_ENDPOINT}`;
1318
+ break;
1319
+ }
1320
+ case SUPPLEMENTARY_SERVICES.RESUME: {
1321
+ request.uri = `${request.uri}/${CALL_HOLD_SERVICE}/${RESUME_ENDPOINT}`;
1322
+ break;
1323
+ }
1324
+ case SUPPLEMENTARY_SERVICES.TRANSFER: {
1325
+ request.uri = `${request.uri}/${CALL_TRANSFER_SERVICE}/${TRANSFER_ENDPOINT}`;
1326
+ const transferContext = context;
1327
+ if (transferContext.destination) {
1328
+ Object.assign(request.body, { blindTransferContext: transferContext });
1329
+ Object.assign(request.body, { transferType: TransferType.BLIND });
1330
+ }
1331
+ else if (transferContext.transferToCallId) {
1332
+ Object.assign(request.body, { consultTransferContext: transferContext });
1333
+ Object.assign(request.body, { transferType: TransferType.CONSULT });
1334
+ }
1335
+ break;
1336
+ }
1337
+ default: {
1338
+ log.warn(`Unknown type for PUT request: ${type}`, {
1339
+ file: CALL_FILE,
1340
+ method: this.postSSRequest.name,
1341
+ });
1342
+ }
1343
+ }
1344
+ return this.webex.request(request);
1345
+ }
1346
+ async postStatus() {
1347
+ return this.webex.request({
1348
+ uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALLS_ENDPOINT_RESOURCE}/${this.callId}/${CALL_STATUS_RESOURCE}`,
1349
+ method: HTTP_METHODS.POST,
1350
+ service: ALLOWED_SERVICES.MOBIUS,
1351
+ headers: {
1352
+ [CISCO_DEVICE_URL]: this.webex.internal.device.url,
1353
+ [SPARK_USER_AGENT]: CALLING_USER_AGENT,
1354
+ },
1355
+ body: {
1356
+ device: {
1357
+ deviceId: this.deviceId,
1358
+ correlationId: this.correlationId,
1359
+ },
1360
+ callId: this.callId,
1361
+ },
1362
+ });
1363
+ }
1364
+ async completeTransfer(transferType, transferCallId, transferTarget) {
1365
+ if (transferType === TransferType.BLIND && transferTarget) {
1366
+ log.info(`Initiating Blind transfer with : ${transferTarget}`, {
1367
+ file: CALL_FILE,
1368
+ method: this.completeTransfer.name,
1369
+ });
1370
+ const context = {
1371
+ transferorCallId: this.getCallId(),
1372
+ destination: transferTarget,
1373
+ };
1374
+ try {
1375
+ await this.postSSRequest(context, SUPPLEMENTARY_SERVICES.TRANSFER);
1376
+ this.metricManager.submitCallMetric(METRIC_EVENT.CALL, TRANSFER_ACTION.BLIND, METRIC_TYPE.BEHAVIORAL, this.getCallId(), this.getCorrelationId(), undefined);
1377
+ }
1378
+ catch (e) {
1379
+ log.warn(`Blind Transfer failed for correlationId ${this.getCorrelationId()}`, {
1380
+ file: CALL_FILE,
1381
+ method: this.completeTransfer.name,
1382
+ });
1383
+ const errData = e;
1384
+ handleCallErrors((error) => {
1385
+ this.emit(EVENT_KEYS.TRANSFER_ERROR, error);
1386
+ this.submitCallErrorMetric(error, TRANSFER_ACTION.BLIND);
1387
+ }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, this.completeTransfer.name, CALL_FILE);
1388
+ }
1389
+ }
1390
+ else if (transferType === TransferType.CONSULT && transferCallId) {
1391
+ log.info(`Initiating Consult transfer between : ${this.callId} and ${transferCallId}`, {
1392
+ file: CALL_FILE,
1393
+ method: this.completeTransfer.name,
1394
+ });
1395
+ const context = {
1396
+ transferorCallId: this.getCallId(),
1397
+ transferToCallId: transferCallId,
1398
+ };
1399
+ try {
1400
+ await this.postSSRequest(context, SUPPLEMENTARY_SERVICES.TRANSFER);
1401
+ this.metricManager.submitCallMetric(METRIC_EVENT.CALL, TRANSFER_ACTION.CONSULT, METRIC_TYPE.BEHAVIORAL, this.getCallId(), this.getCorrelationId(), undefined);
1402
+ }
1403
+ catch (e) {
1404
+ log.warn(`Consult Transfer failed for correlationId ${this.getCorrelationId()}`, {
1405
+ file: CALL_FILE,
1406
+ method: this.completeTransfer.name,
1407
+ });
1408
+ const errData = e;
1409
+ handleCallErrors((error) => {
1410
+ this.emit(EVENT_KEYS.TRANSFER_ERROR, error);
1411
+ this.submitCallErrorMetric(error, TRANSFER_ACTION.CONSULT);
1412
+ }, ERROR_LAYER.CALL_CONTROL, (interval) => undefined, this.getCorrelationId(), errData, this.completeTransfer.name, CALL_FILE);
1413
+ }
1414
+ }
1415
+ else {
1416
+ log.warn(`Invalid information received, transfer failed for correlationId: ${this.getCorrelationId()}`, {
1417
+ file: CALL_FILE,
1418
+ method: this.completeTransfer.name,
1419
+ });
1420
+ }
1421
+ }
1422
+ async getCallStats() {
1423
+ let stats;
1424
+ try {
1425
+ stats = await this.mediaConnection.getStats();
1426
+ }
1427
+ catch (err) {
1428
+ log.warn('Stats collection failed, using dummy stats', {
1429
+ file: CALL_FILE,
1430
+ method: this.getCallStats.name,
1431
+ });
1432
+ }
1433
+ return parseMediaQualityStatistics(stats);
1434
+ }
1435
+ async postMedia(roapMessage) {
1436
+ log.log('Posting message to mobius', {
1437
+ file: CALL_FILE,
1438
+ method: this.postMedia.name,
1439
+ });
1440
+ return this.webex.request({
1441
+ uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALLS_ENDPOINT_RESOURCE}/${this.callId}/${MEDIA_ENDPOINT_RESOURCE}`,
1442
+ method: HTTP_METHODS.POST,
1443
+ service: ALLOWED_SERVICES.MOBIUS,
1444
+ headers: {
1445
+ [CISCO_DEVICE_URL]: this.webex.internal.device.url,
1446
+ [SPARK_USER_AGENT]: CALLING_USER_AGENT,
1447
+ },
1448
+ body: {
1449
+ device: {
1450
+ deviceId: this.deviceId,
1451
+ correlationId: this.correlationId,
1452
+ },
1453
+ callId: this.callId,
1454
+ localMedia: {
1455
+ roap: roapMessage,
1456
+ mediaId: uuid(),
1457
+ },
1458
+ },
1459
+ });
1460
+ }
1461
+ mediaRoapEventsListener() {
1462
+ this.mediaConnection.on(Event.ROAP_MESSAGE_TO_SEND, async (event) => {
1463
+ log.info(`ROAP message to send (rcv from MEDIA-SDK) :
1464
+ \n type: ${event.roapMessage?.messageType}, seq: ${event.roapMessage.seq} , version: ${event.roapMessage.version}`, {});
1465
+ log.info(`SDP message to send : \n ${event.roapMessage?.sdp}`, {
1466
+ file: CALL_FILE,
1467
+ method: this.mediaRoapEventsListener.name,
1468
+ });
1469
+ switch (event.roapMessage.messageType) {
1470
+ case RoapScenario.OK: {
1471
+ const mediaOk = {
1472
+ received: false,
1473
+ message: event.roapMessage,
1474
+ };
1475
+ this.sendMediaStateMachineEvt({ type: 'E_ROAP_OK', data: mediaOk });
1476
+ break;
1477
+ }
1478
+ case RoapScenario.OFFER: {
1479
+ const sdpVideoPortZero = event.roapMessage.sdp.replace(/^m=(video) (?:\d+) /gim, 'm=$1 0 ');
1480
+ event.roapMessage.sdp = sdpVideoPortZero;
1481
+ this.localRoapMessage = event.roapMessage;
1482
+ this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_SETUP', data: event.roapMessage });
1483
+ break;
1484
+ }
1485
+ case RoapScenario.ANSWER:
1486
+ this.localRoapMessage = event.roapMessage;
1487
+ this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_ANSWER', data: event.roapMessage });
1488
+ break;
1489
+ case RoapScenario.ERROR:
1490
+ this.sendMediaStateMachineEvt({ type: 'E_ROAP_ERROR', data: event.roapMessage });
1491
+ break;
1492
+ case RoapScenario.OFFER_RESPONSE:
1493
+ this.localRoapMessage = event.roapMessage;
1494
+ this.sendMediaStateMachineEvt({ type: 'E_SEND_ROAP_OFFER', data: event.roapMessage });
1495
+ break;
1496
+ default:
1497
+ }
1498
+ });
1499
+ }
1500
+ mediaTrackListener() {
1501
+ this.mediaConnection.on(Event.REMOTE_TRACK_ADDED, (e) => {
1502
+ if (e.type === MEDIA_CONNECTION_EVENT_KEYS.MEDIA_TYPE_AUDIO) {
1503
+ this.emit(EVENT_KEYS.REMOTE_MEDIA, e.track);
1504
+ }
1505
+ });
1506
+ }
1507
+ async delete() {
1508
+ const disconnectMetrics = await this.getCallStats();
1509
+ return this.webex.request({
1510
+ uri: `${this.mobiusUrl}${DEVICES_ENDPOINT_RESOURCE}/${this.deviceId}/${CALLS_ENDPOINT_RESOURCE}/${this.callId}`,
1511
+ method: HTTP_METHODS.DELETE,
1512
+ service: ALLOWED_SERVICES.MOBIUS,
1513
+ headers: {
1514
+ [CISCO_DEVICE_URL]: this.webex.internal.device.url,
1515
+ [SPARK_USER_AGENT]: CALLING_USER_AGENT,
1516
+ },
1517
+ body: {
1518
+ device: {
1519
+ deviceId: this.deviceId,
1520
+ correlationId: this.correlationId,
1521
+ },
1522
+ callId: this.callId,
1523
+ metrics: disconnectMetrics,
1524
+ causecode: this.disconnectReason.code,
1525
+ cause: this.disconnectReason.cause,
1526
+ },
1527
+ });
1528
+ }
1529
+ submitCallErrorMetric(error, transferMetricAction) {
1530
+ if (error.getCallError().errorLayer === ERROR_LAYER.CALL_CONTROL) {
1531
+ this.metricManager.submitCallMetric(METRIC_EVENT.CALL_ERROR, transferMetricAction || this.callStateMachine.state.value.toString(), METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, error);
1532
+ }
1533
+ else {
1534
+ this.metricManager.submitMediaMetric(METRIC_EVENT.MEDIA_ERROR, this.mediaStateMachine.state.value.toString(), METRIC_TYPE.BEHAVIORAL, this.callId, this.correlationId, this.localRoapMessage.sdp, this.remoteRoapMessage?.sdp, error);
1535
+ }
1536
+ }
1537
+ handleMidCallEvent(event) {
1538
+ const { eventType, eventData } = event;
1539
+ switch (eventType) {
1540
+ case MidCallEventType.CALL_INFO: {
1541
+ log.log(`Received Midcall CallInfo Event for correlationId : ${this.correlationId}`, {
1542
+ file: CALL_FILE,
1543
+ method: 'handleMidCallEvent',
1544
+ });
1545
+ const callerData = eventData;
1546
+ this.startCallerIdResolution(callerData.callerId);
1547
+ break;
1548
+ }
1549
+ case MidCallEventType.CALL_STATE: {
1550
+ log.log(`Received Midcall call event for correlationId : ${this.correlationId}`, {
1551
+ file: CALL_FILE,
1552
+ method: 'handleMidCallEvent',
1553
+ });
1554
+ const data = eventData;
1555
+ switch (data.callState) {
1556
+ case MOBIUS_MIDCALL_STATE.HELD: {
1557
+ log.log(`Call is successfully held : ${this.correlationId}`, {
1558
+ file: CALL_FILE,
1559
+ method: 'handleMidCallEvent',
1560
+ });
1561
+ this.emit(EVENT_KEYS.HELD, this.correlationId);
1562
+ this.held = true;
1563
+ if (this.supplementaryServicesTimer) {
1564
+ clearTimeout(this.supplementaryServicesTimer);
1565
+ this.supplementaryServicesTimer = undefined;
1566
+ }
1567
+ break;
1568
+ }
1569
+ case MOBIUS_MIDCALL_STATE.CONNECTED: {
1570
+ log.log(`Call is successfully resumed : ${this.correlationId}`, {
1571
+ file: CALL_FILE,
1572
+ method: 'handleMidCallEvent',
1573
+ });
1574
+ this.emit(EVENT_KEYS.RESUMED, this.correlationId);
1575
+ this.held = false;
1576
+ if (this.supplementaryServicesTimer) {
1577
+ clearTimeout(this.supplementaryServicesTimer);
1578
+ this.supplementaryServicesTimer = undefined;
1579
+ }
1580
+ break;
1581
+ }
1582
+ default: {
1583
+ log.warn(`Unknown Supplementary service state: ${data.callState} for correlationId : ${this.correlationId}`, {
1584
+ file: CALL_FILE,
1585
+ method: 'handleMidCallEvent',
1586
+ });
1587
+ }
1588
+ }
1589
+ break;
1590
+ }
1591
+ default: {
1592
+ log.warn(`Unknown Midcall type: ${eventType} for correlationId : ${this.correlationId}`, {
1593
+ file: CALL_FILE,
1594
+ method: 'handleMidCallEvent',
1595
+ });
1596
+ }
1597
+ }
1598
+ }
1599
+ getCallerInfo = () => this.callerInfo;
1600
+ end = () => {
1601
+ this.sendCallStateMachineEvt({ type: 'E_SEND_CALL_DISCONNECT' });
1602
+ };
1603
+ doHoldResume = () => {
1604
+ if (this.held) {
1605
+ this.sendCallStateMachineEvt({ type: 'E_CALL_RESUME' });
1606
+ }
1607
+ else {
1608
+ this.sendCallStateMachineEvt({ type: 'E_CALL_HOLD' });
1609
+ }
1610
+ };
1611
+ startCallerIdResolution(callerInfo) {
1612
+ this.callerInfo = this.callerId.fetchCallerDetails(callerInfo);
1613
+ }
1614
+ sendDTMF(tone) {
1615
+ if (!this.connected) {
1616
+ log.warn(`Can't send DTMF as call is not yet connected`, {
1617
+ file: CALL_FILE,
1618
+ method: 'sendDTMF',
1619
+ });
1620
+ return;
1621
+ }
1622
+ log.info(`Sending DTMF : ${tone}`, {
1623
+ file: CALL_FILE,
1624
+ method: 'sendDTMF',
1625
+ });
1626
+ this.mediaConnection.insertDTMF(tone);
1627
+ }
1628
+ mute = (localAudioStream) => {
1629
+ const localAudioTrack = localAudioStream.outputStream.getAudioTracks()[0];
1630
+ if (this.muted) {
1631
+ localAudioTrack.enabled = true;
1632
+ this.muted = false;
1633
+ }
1634
+ else {
1635
+ localAudioTrack.enabled = false;
1636
+ this.muted = true;
1637
+ }
1638
+ };
1639
+ setBroadworksCorrelationInfo(broadworksCorrelationInfo) {
1640
+ this.broadworksCorrelationInfo = broadworksCorrelationInfo;
1641
+ }
1642
+ getBroadworksCorrelationInfo() {
1643
+ return this.broadworksCorrelationInfo;
1644
+ }
1645
+ getCallRtpStats() {
1646
+ return this.getCallStats();
1647
+ }
1648
+ }
1649
+ export const createCall = (activeUrl, webex, dest, dir, deviceId, deleteCb, indicator) => new Call(activeUrl, webex, dest, dir, deviceId, deleteCb, indicator);