@webex/plugin-meetings 3.0.0-stream-classes.4 → 3.0.0-test.1

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 (239) hide show
  1. package/README.md +12 -0
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/common/errors/no-meeting-info.js +51 -0
  5. package/dist/common/errors/no-meeting-info.js.map +1 -0
  6. package/dist/common/errors/reclaim-host-role-errors.js +158 -0
  7. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
  8. package/dist/common/errors/webex-errors.js +23 -3
  9. package/dist/common/errors/webex-errors.js.map +1 -1
  10. package/dist/common/logs/request.js +5 -1
  11. package/dist/common/logs/request.js.map +1 -1
  12. package/dist/config.js +1 -1
  13. package/dist/config.js.map +1 -1
  14. package/dist/constants.js +69 -9
  15. package/dist/constants.js.map +1 -1
  16. package/dist/index.js +11 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/interceptors/index.js +15 -0
  19. package/dist/interceptors/index.js.map +1 -0
  20. package/dist/interceptors/locusRetry.js +93 -0
  21. package/dist/interceptors/locusRetry.js.map +1 -0
  22. package/dist/interpretation/index.js +16 -2
  23. package/dist/interpretation/index.js.map +1 -1
  24. package/dist/interpretation/siLanguage.js +1 -1
  25. package/dist/locus-info/index.js +40 -11
  26. package/dist/locus-info/index.js.map +1 -1
  27. package/dist/locus-info/mediaSharesUtils.js +15 -1
  28. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  29. package/dist/locus-info/parser.js +42 -21
  30. package/dist/locus-info/parser.js.map +1 -1
  31. package/dist/media/index.js +10 -6
  32. package/dist/media/index.js.map +1 -1
  33. package/dist/media/properties.js +13 -3
  34. package/dist/media/properties.js.map +1 -1
  35. package/dist/mediaQualityMetrics/config.js +135 -330
  36. package/dist/mediaQualityMetrics/config.js.map +1 -1
  37. package/dist/meeting/in-meeting-actions.js +4 -0
  38. package/dist/meeting/in-meeting-actions.js.map +1 -1
  39. package/dist/meeting/index.js +2187 -1074
  40. package/dist/meeting/index.js.map +1 -1
  41. package/dist/meeting/muteState.js +37 -25
  42. package/dist/meeting/muteState.js.map +1 -1
  43. package/dist/meeting/request.js +34 -19
  44. package/dist/meeting/request.js.map +1 -1
  45. package/dist/meeting/util.js +71 -0
  46. package/dist/meeting/util.js.map +1 -1
  47. package/dist/meeting-info/index.js +48 -23
  48. package/dist/meeting-info/index.js.map +1 -1
  49. package/dist/meeting-info/meeting-info-v2.js +25 -4
  50. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  51. package/dist/meeting-info/utilv2.js +1 -1
  52. package/dist/meeting-info/utilv2.js.map +1 -1
  53. package/dist/meetings/collection.js +17 -0
  54. package/dist/meetings/collection.js.map +1 -1
  55. package/dist/meetings/index.js +142 -57
  56. package/dist/meetings/index.js.map +1 -1
  57. package/dist/meetings/util.js +2 -6
  58. package/dist/meetings/util.js.map +1 -1
  59. package/dist/member/index.js +9 -0
  60. package/dist/member/index.js.map +1 -1
  61. package/dist/member/util.js +11 -0
  62. package/dist/member/util.js.map +1 -1
  63. package/dist/members/index.js +17 -1
  64. package/dist/members/index.js.map +1 -1
  65. package/dist/members/types.js.map +1 -1
  66. package/dist/members/util.js +15 -4
  67. package/dist/members/util.js.map +1 -1
  68. package/dist/metrics/constants.js +15 -1
  69. package/dist/metrics/constants.js.map +1 -1
  70. package/dist/multistream/mediaRequestManager.js +1 -1
  71. package/dist/multistream/mediaRequestManager.js.map +1 -1
  72. package/dist/multistream/remoteMediaGroup.js +16 -2
  73. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  74. package/dist/multistream/remoteMediaManager.js +222 -73
  75. package/dist/multistream/remoteMediaManager.js.map +1 -1
  76. package/dist/multistream/sendSlotManager.js +22 -0
  77. package/dist/multistream/sendSlotManager.js.map +1 -1
  78. package/dist/reachability/clusterReachability.js +356 -0
  79. package/dist/reachability/clusterReachability.js.map +1 -0
  80. package/dist/reachability/index.js +262 -432
  81. package/dist/reachability/index.js.map +1 -1
  82. package/dist/reachability/request.js +1 -1
  83. package/dist/reachability/request.js.map +1 -1
  84. package/dist/reachability/util.js +29 -0
  85. package/dist/reachability/util.js.map +1 -0
  86. package/dist/reconnection-manager/index.js +113 -96
  87. package/dist/reconnection-manager/index.js.map +1 -1
  88. package/dist/roap/index.js +57 -25
  89. package/dist/roap/index.js.map +1 -1
  90. package/dist/roap/request.js +5 -13
  91. package/dist/roap/request.js.map +1 -1
  92. package/dist/roap/turnDiscovery.js +173 -81
  93. package/dist/roap/turnDiscovery.js.map +1 -1
  94. package/dist/rtcMetrics/index.js +68 -6
  95. package/dist/rtcMetrics/index.js.map +1 -1
  96. package/dist/statsAnalyzer/index.js +338 -289
  97. package/dist/statsAnalyzer/index.js.map +1 -1
  98. package/dist/statsAnalyzer/mqaUtil.js +296 -156
  99. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  100. package/dist/types/common/errors/no-meeting-info.d.ts +14 -0
  101. package/dist/types/common/errors/reclaim-host-role-errors.d.ts +60 -0
  102. package/dist/types/common/errors/webex-errors.d.ts +13 -1
  103. package/dist/types/common/logs/request.d.ts +2 -0
  104. package/dist/types/config.d.ts +1 -1
  105. package/dist/types/constants.d.ts +66 -13
  106. package/dist/types/index.d.ts +1 -1
  107. package/dist/types/interceptors/index.d.ts +2 -0
  108. package/dist/types/interceptors/locusRetry.d.ts +27 -0
  109. package/dist/types/locus-info/index.d.ts +1 -1
  110. package/dist/types/locus-info/parser.d.ts +3 -2
  111. package/dist/types/mediaQualityMetrics/config.d.ts +99 -223
  112. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  113. package/dist/types/meeting/index.d.ts +285 -34
  114. package/dist/types/meeting/locusMediaRequest.d.ts +1 -2
  115. package/dist/types/meeting/muteState.d.ts +2 -8
  116. package/dist/types/meeting/request.d.ts +4 -1
  117. package/dist/types/meeting/util.d.ts +25 -1
  118. package/dist/types/meeting-info/index.d.ts +7 -0
  119. package/dist/types/meeting-info/meeting-info-v2.d.ts +1 -0
  120. package/dist/types/meetings/collection.d.ts +9 -0
  121. package/dist/types/meetings/index.d.ts +42 -14
  122. package/dist/types/member/index.d.ts +1 -0
  123. package/dist/types/members/types.d.ts +1 -0
  124. package/dist/types/members/util.d.ts +5 -0
  125. package/dist/types/metrics/constants.d.ts +15 -0
  126. package/dist/types/multistream/mediaRequestManager.d.ts +2 -0
  127. package/dist/types/multistream/remoteMediaGroup.d.ts +2 -0
  128. package/dist/types/multistream/remoteMediaManager.d.ts +25 -1
  129. package/dist/types/multistream/sendSlotManager.d.ts +9 -0
  130. package/dist/types/reachability/clusterReachability.d.ts +109 -0
  131. package/dist/types/reachability/index.d.ts +59 -112
  132. package/dist/types/reachability/request.d.ts +1 -1
  133. package/dist/types/reachability/util.d.ts +8 -0
  134. package/dist/types/reconnection-manager/index.d.ts +10 -0
  135. package/dist/types/roap/index.d.ts +2 -1
  136. package/dist/types/roap/request.d.ts +2 -1
  137. package/dist/types/roap/turnDiscovery.d.ts +21 -4
  138. package/dist/types/rtcMetrics/index.d.ts +15 -1
  139. package/dist/types/statsAnalyzer/index.d.ts +28 -11
  140. package/dist/types/statsAnalyzer/mqaUtil.d.ts +28 -4
  141. package/dist/types/webinar/collection.d.ts +16 -0
  142. package/dist/types/webinar/index.d.ts +5 -0
  143. package/dist/webinar/collection.js +44 -0
  144. package/dist/webinar/collection.js.map +1 -0
  145. package/dist/webinar/index.js +69 -0
  146. package/dist/webinar/index.js.map +1 -0
  147. package/package.json +3 -2
  148. package/src/common/errors/no-meeting-info.ts +24 -0
  149. package/src/common/errors/reclaim-host-role-errors.ts +134 -0
  150. package/src/common/errors/webex-errors.ts +19 -2
  151. package/src/common/logs/request.ts +5 -1
  152. package/src/config.ts +1 -1
  153. package/src/constants.ts +71 -6
  154. package/src/index.ts +5 -0
  155. package/src/interceptors/index.ts +3 -0
  156. package/src/interceptors/locusRetry.ts +67 -0
  157. package/src/interpretation/index.ts +18 -1
  158. package/src/locus-info/index.ts +52 -16
  159. package/src/locus-info/mediaSharesUtils.ts +16 -0
  160. package/src/locus-info/parser.ts +47 -21
  161. package/src/media/index.ts +8 -6
  162. package/src/media/properties.ts +17 -2
  163. package/src/mediaQualityMetrics/config.ts +103 -238
  164. package/src/meeting/in-meeting-actions.ts +8 -0
  165. package/src/meeting/index.ts +1510 -529
  166. package/src/meeting/muteState.ts +34 -20
  167. package/src/meeting/request.ts +19 -1
  168. package/src/meeting/util.ts +97 -0
  169. package/src/meeting-info/index.ts +47 -20
  170. package/src/meeting-info/meeting-info-v2.ts +27 -5
  171. package/src/meeting-info/utilv2.ts +1 -1
  172. package/src/meetings/collection.ts +13 -0
  173. package/src/meetings/index.ts +112 -31
  174. package/src/meetings/util.ts +2 -8
  175. package/src/member/index.ts +9 -0
  176. package/src/member/util.ts +14 -0
  177. package/src/members/index.ts +29 -2
  178. package/src/members/types.ts +1 -0
  179. package/src/members/util.ts +15 -1
  180. package/src/metrics/constants.ts +14 -0
  181. package/src/multistream/mediaRequestManager.ts +4 -1
  182. package/src/multistream/remoteMediaGroup.ts +19 -0
  183. package/src/multistream/remoteMediaManager.ts +141 -18
  184. package/src/multistream/sendSlotManager.ts +29 -0
  185. package/src/reachability/clusterReachability.ts +320 -0
  186. package/src/reachability/index.ts +221 -382
  187. package/src/reachability/request.ts +1 -1
  188. package/src/reachability/util.ts +24 -0
  189. package/src/reconnection-manager/index.ts +87 -83
  190. package/src/roap/index.ts +60 -24
  191. package/src/roap/request.ts +3 -16
  192. package/src/roap/turnDiscovery.ts +112 -39
  193. package/src/rtcMetrics/index.ts +71 -5
  194. package/src/statsAnalyzer/index.ts +430 -427
  195. package/src/statsAnalyzer/mqaUtil.ts +317 -168
  196. package/src/webinar/collection.ts +31 -0
  197. package/src/webinar/index.ts +62 -0
  198. package/test/integration/spec/converged-space-meetings.js +7 -7
  199. package/test/integration/spec/journey.js +86 -104
  200. package/test/integration/spec/space-meeting.js +9 -9
  201. package/test/unit/spec/interceptors/locusRetry.ts +131 -0
  202. package/test/unit/spec/interpretation/index.ts +36 -3
  203. package/test/unit/spec/locus-info/index.js +205 -12
  204. package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
  205. package/test/unit/spec/locus-info/mediaSharesUtils.ts +10 -0
  206. package/test/unit/spec/locus-info/parser.js +54 -13
  207. package/test/unit/spec/media/index.ts +20 -4
  208. package/test/unit/spec/media/properties.ts +2 -2
  209. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  210. package/test/unit/spec/meeting/index.js +4027 -1075
  211. package/test/unit/spec/meeting/muteState.js +219 -67
  212. package/test/unit/spec/meeting/request.js +63 -12
  213. package/test/unit/spec/meeting/utils.js +93 -0
  214. package/test/unit/spec/meeting-info/index.js +180 -61
  215. package/test/unit/spec/meeting-info/meetinginfov2.js +196 -53
  216. package/test/unit/spec/meetings/collection.js +12 -0
  217. package/test/unit/spec/meetings/index.js +619 -206
  218. package/test/unit/spec/meetings/utils.js +35 -12
  219. package/test/unit/spec/member/index.js +8 -7
  220. package/test/unit/spec/member/util.js +32 -0
  221. package/test/unit/spec/members/index.js +130 -17
  222. package/test/unit/spec/members/utils.js +26 -0
  223. package/test/unit/spec/multistream/mediaRequestManager.ts +20 -2
  224. package/test/unit/spec/multistream/remoteMediaGroup.ts +80 -1
  225. package/test/unit/spec/multistream/remoteMediaManager.ts +210 -3
  226. package/test/unit/spec/multistream/sendSlotManager.ts +50 -18
  227. package/test/unit/spec/reachability/clusterReachability.ts +279 -0
  228. package/test/unit/spec/reachability/index.ts +505 -135
  229. package/test/unit/spec/reachability/util.ts +40 -0
  230. package/test/unit/spec/reconnection-manager/index.js +74 -17
  231. package/test/unit/spec/roap/index.ts +181 -61
  232. package/test/unit/spec/roap/request.ts +27 -3
  233. package/test/unit/spec/roap/turnDiscovery.ts +362 -101
  234. package/test/unit/spec/rtcMetrics/index.ts +57 -3
  235. package/test/unit/spec/stats-analyzer/index.js +1225 -12
  236. package/test/unit/spec/webinar/collection.ts +13 -0
  237. package/test/unit/spec/webinar/index.ts +60 -0
  238. package/test/utils/integrationTestUtils.js +4 -4
  239. package/test/utils/webex-test-users.js +12 -4
@@ -0,0 +1,40 @@
1
+ import {assert} from '@webex/test-helper-chai';
2
+
3
+ import {convertStunUrlToTurn} from '@webex/plugin-meetings/src/reachability/util';
4
+
5
+ describe('plugin-meetings/src/reachability/util', () => {
6
+ describe('#convertStunUrlToTurn()', () => {
7
+ [
8
+ {
9
+ title: 'a stun url with port',
10
+ stunUrl: 'stun:external-media91.public.wjfkm-a-10.prod.infra.webex.com:5004',
11
+ expectedUrlPart: 'external-media91.public.wjfkm-a-10.prod.infra.webex.com:5004',
12
+ },
13
+ {
14
+ title: 'a stun url without port',
15
+ stunUrl: 'stun:something.somewhere.com',
16
+ expectedUrlPart: 'something.somewhere.com',
17
+ },
18
+ ].forEach(({title, stunUrl, expectedUrlPart}) => {
19
+ it(`should convert ${title} to a TCP turn url`, () => {
20
+ const turnUrl = convertStunUrlToTurn(stunUrl, 'tcp');
21
+
22
+ assert.equal(turnUrl, `turn:${expectedUrlPart}?transport=tcp`);
23
+ });
24
+
25
+ it(`should convert ${title} to a UDP turn url`, () => {
26
+ const turnUrl = convertStunUrlToTurn(stunUrl, 'udp');
27
+
28
+ assert.equal(turnUrl, `turn:${expectedUrlPart}`);
29
+ });
30
+ });
31
+
32
+ it('show fail if stunUrl is not a valid url', () => {
33
+ assert.throws(() => convertStunUrlToTurn('not a url', 'tcp'), 'Invalid URL: not a url');
34
+ });
35
+
36
+ it('show fail if stunUrl is not a STUN url', () => {
37
+ assert.throws(() => convertStunUrlToTurn('http://webex.com', 'tcp'), 'Not a STUN URL: http://webex.com');
38
+ });
39
+ });
40
+ });
@@ -3,6 +3,9 @@ import chai from 'chai';
3
3
  import chaiAsPromised from 'chai-as-promised';
4
4
  import sinon from 'sinon';
5
5
  import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
6
+ import { RECONNECTION } from '../../../../src/constants';
7
+ import LoggerProxy from '../../../../src/common/logs/logger-proxy';
8
+ import LoggerConfig from '../../../../src/common/logs/logger-config';
6
9
 
7
10
  const {assert} = chai;
8
11
 
@@ -11,8 +14,16 @@ sinon.assert.expose(chai.assert, {prefix: ''});
11
14
 
12
15
  describe('plugin-meetings', () => {
13
16
  describe('ReconnectionManager.reconnect', () => {
17
+ const sandbox = sinon.createSandbox();
14
18
  let fakeMediaConnection;
15
19
  let fakeMeeting;
20
+ let loggerSpy;
21
+
22
+ before(() => {
23
+ LoggerConfig.set({ enable: false });
24
+ LoggerProxy.set();
25
+ loggerSpy = sandbox.spy(LoggerProxy.logger, 'info');
26
+ });
16
27
 
17
28
  beforeEach(() => {
18
29
  fakeMediaConnection = {
@@ -64,6 +75,7 @@ describe('plugin-meetings', () => {
64
75
  meetings: {
65
76
  getMeetingByType: sinon.stub().returns(true),
66
77
  syncMeetings: sinon.stub().resolves({}),
78
+ startReachability: sinon.stub().resolves({}),
67
79
  },
68
80
  internal: {
69
81
  newMetrics: {
@@ -74,22 +86,38 @@ describe('plugin-meetings', () => {
74
86
  };
75
87
  });
76
88
 
77
- it('syncs meetings if it is not an unverified guest', async () => {
89
+ afterEach(() => {
90
+ sandbox.reset();
91
+ });
92
+
93
+ it('calls syncMeetings', async () => {
78
94
  const rm = new ReconnectionManager(fakeMeeting);
79
95
 
80
96
  await rm.reconnect();
81
97
 
82
98
  assert.calledOnce(rm.webex.meetings.syncMeetings);
99
+ assert.calledWith(rm.webex.meetings.syncMeetings, {keepOnlyLocusMeetings: false});
83
100
  });
84
101
 
85
- it('does not sync meetings if it is an unverified guest', async () => {
102
+ it('calls startReachability on reconnect', async () => {
86
103
  const rm = new ReconnectionManager(fakeMeeting);
87
104
 
88
- rm.webex.credentials.isUnverifiedGuest = true;
105
+ await rm.reconnect();
106
+
107
+ assert.calledOnce(rm.webex.meetings.startReachability);
108
+ });
109
+
110
+ it('continues with reconnection attempt if startReachability throws an error', async () => {
111
+ const reachabilityError = new Error();
112
+ fakeMeeting.webex.meetings.startReachability = sinon.stub().throws(reachabilityError);
113
+
114
+ const rm = new ReconnectionManager(fakeMeeting);
89
115
 
90
116
  await rm.reconnect();
91
117
 
92
- assert.notCalled(rm.webex.meetings.syncMeetings);
118
+ assert.calledOnce(rm.webex.meetings.startReachability);
119
+ assert.calledWith(loggerSpy, 'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ', reachabilityError);
120
+ assert.calledWith(loggerSpy, 'ReconnectionManager:index#executeReconnection --> Attempting to reconnect to meeting.');
93
121
  });
94
122
 
95
123
  it('uses correct TURN TLS information on the reconnection', async () => {
@@ -98,6 +126,7 @@ describe('plugin-meetings', () => {
98
126
  await rm.reconnect();
99
127
 
100
128
  assert.calledOnce(fakeMeeting.roap.doTurnDiscovery);
129
+ assert.calledWith(fakeMeeting.roap.doTurnDiscovery, fakeMeeting, true, true);
101
130
  assert.calledOnce(fakeMediaConnection.reconnect);
102
131
  assert.calledWith(fakeMediaConnection.reconnect, [
103
132
  {
@@ -113,16 +142,6 @@ describe('plugin-meetings', () => {
113
142
  meetingId: rm.meeting.id,
114
143
  },
115
144
  });
116
-
117
- assert.calledWith(fakeMeeting.webex.internal.newMetrics.submitClientEvent, {
118
- name: 'client.media.recovered',
119
- payload: {
120
- recoveredBy: 'new',
121
- },
122
- options: {
123
- meetingId: rm.meeting.id,
124
- },
125
- });
126
145
  });
127
146
 
128
147
  it('does not clear previous requests and re-request media for non-multistream meetings', async () => {
@@ -149,7 +168,6 @@ describe('plugin-meetings', () => {
149
168
  assert.calledOnce(fakeMeeting.mediaRequestManagers.video.commit);
150
169
  });
151
170
 
152
-
153
171
  it('sends the correct client event when reconnection fails', async () => {
154
172
  sinon.stub(ReconnectionManager.prototype, 'executeReconnection').rejects();
155
173
  fakeMeeting.isMultistream = true;
@@ -186,9 +204,10 @@ describe('plugin-meetings', () => {
186
204
  */
187
205
  describe('ReconnectionManager', () => {
188
206
  let reconnectionManager;
207
+ let fakeMeeting;
189
208
 
190
209
  beforeEach(() => {
191
- reconnectionManager = new ReconnectionManager({
210
+ fakeMeeting = {
192
211
  config: {
193
212
  reconnection: {
194
213
  enabled: true,
@@ -203,7 +222,9 @@ describe('plugin-meetings', () => {
203
222
  },
204
223
  },
205
224
  },
206
- });
225
+ };
226
+
227
+ reconnectionManager = new ReconnectionManager(fakeMeeting);
207
228
  });
208
229
 
209
230
  describe('iceReconnected()', () => {
@@ -309,5 +330,41 @@ describe('plugin-meetings', () => {
309
330
  });
310
331
  });
311
332
  });
333
+
334
+ describe('setStatus()', () => {
335
+ beforeEach(() => {
336
+ reconnectionManager.status = RECONNECTION.STATE.DEFAULT_STATUS;
337
+ });
338
+
339
+ it('should correctly change status to in progress', () => {
340
+ reconnectionManager.setStatus(RECONNECTION.STATE.IN_PROGRESS);
341
+
342
+ assert.equal(reconnectionManager.status, RECONNECTION.STATE.IN_PROGRESS);
343
+ });
344
+
345
+ it('should correctly change status to complete', () => {
346
+ reconnectionManager.setStatus(RECONNECTION.STATE.COMPLETE);
347
+
348
+ assert.equal(reconnectionManager.status, RECONNECTION.STATE.COMPLETE);
349
+ });
350
+
351
+ it('should correctly change status to failure', () => {
352
+ reconnectionManager.setStatus(RECONNECTION.STATE.FAILURE);
353
+
354
+ assert.equal(reconnectionManager.status, RECONNECTION.STATE.FAILURE);
355
+ });
356
+ });
357
+
358
+ describe('cleanUp()', () => {
359
+ it('should call reset and keep reference to meeting object', () => {
360
+ const resetSpy = sinon.spy(reconnectionManager, 'reset');
361
+ assert.equal(reconnectionManager.meeting, fakeMeeting);
362
+
363
+ reconnectionManager.cleanUp();
364
+
365
+ assert.equal(reconnectionManager.meeting, fakeMeeting);
366
+ assert.calledOnce(reconnectionManager.reset);
367
+ });
368
+ });
312
369
  });
313
370
  });
@@ -7,38 +7,35 @@ import RoapRequest from '@webex/plugin-meetings/src/roap/request';
7
7
  import Roap from '@webex/plugin-meetings/src/roap/';
8
8
  import Meeting from '@webex/plugin-meetings/src/meeting';
9
9
  import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
10
+ import Metrics from '@webex/plugin-meetings/src/metrics';
11
+ import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
10
12
 
11
13
  import { IP_VERSION } from '../../../../src/constants';
12
14
 
13
15
  describe('Roap', () => {
14
16
  describe('doTurnDiscovery', () => {
15
- it('calls this.turnDiscovery.doTurnDiscovery() and forwards all the arguments', async () => {
16
- const webex = new MockWebex({});
17
+ [false, true].forEach(function (isReconnecting) {
18
+ [false, true, undefined].forEach(function (isForced) {
19
+ it(`calls this.turnDiscovery.doTurnDiscovery() and forwards all the arguments when isReconnecting = ${isReconnecting} and isForced = ${isForced}`, async () => {
20
+ const webex = new MockWebex({});
17
21
 
18
- const RESULT = {something: 'some value'};
19
- const meeting = {id: 'some meeting id'} as Meeting;
22
+ const RESULT = {something: 'some value'};
23
+ const meeting = {id: 'some meeting id'} as Meeting;
20
24
 
21
- const doTurnDiscoveryStub = sinon
22
- .stub(TurnDiscovery.prototype, 'doTurnDiscovery')
23
- .resolves(RESULT);
25
+ const doTurnDiscoveryStub = sinon
26
+ .stub(TurnDiscovery.prototype, 'doTurnDiscovery')
27
+ .resolves(RESULT);
24
28
 
25
- const roap = new Roap({}, {parent: webex});
29
+ const roap = new Roap({}, {parent: webex});
26
30
 
27
- // call with isReconnecting: true
28
- const result = await roap.doTurnDiscovery(meeting, true);
31
+ const result = await roap.doTurnDiscovery(meeting, isReconnecting, isForced);
29
32
 
30
- assert.calledOnceWithExactly(doTurnDiscoveryStub, meeting, true);
31
- assert.deepEqual(result, RESULT);
33
+ assert.calledOnceWithExactly(doTurnDiscoveryStub, meeting, isReconnecting, isForced);
34
+ assert.deepEqual(result, RESULT);
32
35
 
33
- doTurnDiscoveryStub.resetHistory();
34
-
35
- // and with isReconnecting: false
36
- const result2 = await roap.doTurnDiscovery(meeting, false);
37
-
38
- assert.calledOnceWithExactly(doTurnDiscoveryStub, meeting, false);
39
- assert.deepEqual(result2, RESULT);
40
-
41
- sinon.restore();
36
+ sinon.restore();
37
+ });
38
+ });
42
39
  });
43
40
  });
44
41
 
@@ -47,6 +44,9 @@ describe('Roap', () => {
47
44
  let meeting;
48
45
 
49
46
  let webex;
47
+ let roap;
48
+
49
+ const fakeLocus = {id: 'fake locus'};
50
50
 
51
51
  beforeEach(() => {
52
52
  webex = new MockWebex({});
@@ -55,68 +55,188 @@ describe('Roap', () => {
55
55
  correlationId: 'correlation id',
56
56
  selfUrl: 'self url',
57
57
  mediaId: 'media id',
58
- audio:{
58
+ audio: {
59
59
  isLocallyMuted: () => true,
60
60
  },
61
- video:{
61
+ video: {
62
62
  isLocallyMuted: () => false,
63
63
  },
64
+ isMultistream: true,
64
65
  setRoapSeq: sinon.stub(),
65
- config: {experimental: {enableTurnDiscovery: false}},
66
66
  locusMediaRequest: {fake: true},
67
- webex: { meetings: { reachability: { isAnyClusterReachable: () => true}}},
67
+ webex: {meetings: {reachability: {isAnyPublicClusterReachable: () => true}}},
68
+ updateMediaConnections: sinon.stub(),
68
69
  };
69
70
 
70
71
  sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.unknown);
72
+ sinon.stub(Metrics, 'sendBehavioralMetric');
71
73
 
72
74
  sendRoapStub = sinon.stub(RoapRequest.prototype, 'sendRoap').resolves({});
73
75
  meeting.setRoapSeq.resetHistory();
76
+
77
+ roap = new Roap({}, {parent: webex});
78
+ sinon.stub(roap.turnDiscovery, 'isSkipped').resolves(false);
74
79
  });
75
80
 
76
81
  afterEach(() => {
77
82
  sinon.restore();
78
83
  });
79
84
 
80
- [
81
- {reconnect: true, turnDiscoverySkipped: false, expectEmptyMediaId: false},
82
- {reconnect: true, turnDiscoverySkipped: true, expectEmptyMediaId: true},
83
- {reconnect: false, turnDiscoverySkipped: false, expectEmptyMediaId: false},
84
- {reconnect: false, turnDiscoverySkipped: true, expectEmptyMediaId: false},
85
- ].forEach(({reconnect, turnDiscoverySkipped, expectEmptyMediaId}) =>
86
- it(`sends roap OFFER with ${expectEmptyMediaId ? 'empty ' : ''}mediaId when ${
87
- reconnect ? '' : 'not '
88
- }reconnecting and TURN discovery is ${
89
- turnDiscoverySkipped ? 'skipped' : 'not skipped'
90
- }`, async () => {
91
- const roap = new Roap({}, {parent: webex});
92
-
93
- sinon.stub(roap.turnDiscovery, 'isSkipped').resolves(turnDiscoverySkipped);
94
-
95
- await roap.sendRoapMediaRequest({
96
- meeting,
97
- sdp: 'sdp',
98
- reconnect,
99
- seq: 2,
100
- tieBreaker: 4294967294,
101
- });
102
-
103
- const expectedRoapMessage = {
104
- messageType: 'OFFER',
105
- sdps: ['sdp'],
106
- version: '2',
107
- seq: 2,
108
- tieBreaker: 4294967294,
109
- };
85
+ it(`sends roap OFFER`, async () => {
86
+ await roap.sendRoapMediaRequest({
87
+ meeting,
88
+ sdp: 'sdp',
89
+ seq: 2,
90
+ tieBreaker: 4294967294,
91
+ });
92
+
93
+ const expectedRoapMessage = {
94
+ messageType: 'OFFER',
95
+ sdps: ['sdp'],
96
+ version: '2',
97
+ seq: 2,
98
+ tieBreaker: 4294967294,
99
+ headers: ['includeAnswerInHttpResponse', 'noOkInTransaction'],
100
+ };
110
101
 
111
- assert.calledOnce(sendRoapStub);
112
- assert.calledWith(sendRoapStub, sinon.match({
102
+ assert.calledOnce(sendRoapStub);
103
+ assert.calledWith(
104
+ sendRoapStub,
105
+ sinon.match({
113
106
  roapMessage: expectedRoapMessage,
114
107
  locusSelfUrl: meeting.selfUrl,
115
- mediaId: expectEmptyMediaId ? '' : meeting.mediaId,
108
+ mediaId: meeting.mediaId,
116
109
  meetingId: meeting.id,
117
110
  locusMediaRequest: meeting.locusMediaRequest,
118
- }));
119
- })
120
- );
111
+ })
112
+ );
113
+ });
114
+
115
+ it('reads SDP answer from the http response', async () => {
116
+ const roapAnswer = {
117
+ seq: 5,
118
+ messageType: 'ANSWER',
119
+ sdps: ['sdp answer'],
120
+ errorType: 'error type', // normally ANSWER would not have errorType or errorCause (only error messages have these)
121
+ errorCause: 'error cause', // but we're just testing here that all the fields are forwarded to the caller of sendRoapMediaRequest()
122
+ headers: ['header1', 'header2'],
123
+ };
124
+ const fakeMediaConnections = [
125
+ {
126
+ remoteSdp: JSON.stringify({
127
+ roapMessage: roapAnswer,
128
+ }),
129
+ },
130
+ ];
131
+
132
+ sendRoapStub.resolves({
133
+ mediaConnections: fakeMediaConnections,
134
+ locus: fakeLocus,
135
+ });
136
+
137
+ const result = await roap.sendRoapMediaRequest({
138
+ meeting,
139
+ sdp: 'sdp',
140
+ reconnect: false,
141
+ seq: 1,
142
+ tieBreaker: 4294967294,
143
+ });
144
+
145
+ assert.calledOnce(sendRoapStub);
146
+ assert.calledOnceWithExactly(meeting.updateMediaConnections, fakeMediaConnections);
147
+ assert.deepEqual(result, {
148
+ locus: fakeLocus,
149
+ roapAnswer: {
150
+ seq: 5,
151
+ messageType: 'ANSWER',
152
+ sdp: 'sdp answer',
153
+ errorType: 'error type',
154
+ errorCause: 'error cause',
155
+ headers: ['header1', 'header2'],
156
+ },
157
+ });
158
+ });
159
+
160
+ it('handles the case when there is no answer in the http response', async () => {
161
+ const fakeMediaConnections = [
162
+ {
163
+ // this is the actual value Locus returns to us when they don't send Roap ANSWER in the http response
164
+ remoteSdp:
165
+ '{"audioMuted":false,"videoMuted":false,"csis":[],"dtmfReceiveSupported":true,"type":"SDP"}',
166
+ },
167
+ ];
168
+
169
+ sendRoapStub.resolves({
170
+ mediaConnections: fakeMediaConnections,
171
+ locus: fakeLocus,
172
+ });
173
+
174
+ const result = await roap.sendRoapMediaRequest({
175
+ meeting,
176
+ sdp: 'sdp',
177
+ reconnect: false,
178
+ seq: 1,
179
+ tieBreaker: 4294967294,
180
+ });
181
+
182
+ assert.calledOnce(sendRoapStub);
183
+ assert.calledOnceWithExactly(meeting.updateMediaConnections, fakeMediaConnections);
184
+ assert.deepEqual(result, {
185
+ locus: fakeLocus,
186
+ roapAnswer: undefined,
187
+ });
188
+ assert.calledOnceWithExactly(
189
+ Metrics.sendBehavioralMetric,
190
+ BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING,
191
+ {
192
+ correlationId: meeting.correlationId,
193
+ messageType: 'ANSWER',
194
+ isMultistream: meeting.isMultistream,
195
+ }
196
+ );
197
+ });
198
+
199
+ describe('does not crash when http response is missing things', () => {
200
+ [
201
+ {mediaConnections: undefined, title: 'mediaConnections are undefined'},
202
+ {mediaConnections: [], title: 'mediaConnections are empty array'},
203
+ {mediaConnections: [{}], title: 'mediaConnections[0] has no remoteSdp'},
204
+ {
205
+ mediaConnections: [{remoteSdp: '{}'}],
206
+ title: 'mediaConnections[0].remoteSdp is an empty json',
207
+ },
208
+ ].forEach(({mediaConnections, title}) =>
209
+ it(title, async () => {
210
+ sendRoapStub.resolves({
211
+ mediaConnections,
212
+ locus: fakeLocus,
213
+ });
214
+
215
+ const result = await roap.sendRoapMediaRequest({
216
+ meeting,
217
+ sdp: 'sdp',
218
+ reconnect: false,
219
+ seq: 1,
220
+ tieBreaker: 4294967294,
221
+ });
222
+
223
+ assert.calledOnce(sendRoapStub);
224
+ assert.deepEqual(result, {
225
+ locus: fakeLocus,
226
+ roapAnswer: undefined,
227
+ });
228
+
229
+ assert.calledOnceWithExactly(
230
+ Metrics.sendBehavioralMetric,
231
+ BEHAVIORAL_METRICS.ROAP_HTTP_RESPONSE_MISSING,
232
+ {
233
+ correlationId: meeting.correlationId,
234
+ messageType: 'ANSWER',
235
+ isMultistream: meeting.isMultistream,
236
+ }
237
+ );
238
+ })
239
+ );
240
+ });
121
241
  });
122
242
  });
@@ -65,7 +65,9 @@ describe('plugin-meetings/roap', () => {
65
65
  REACHABILITY.localStorageResult,
66
66
  JSON.stringify({
67
67
  clusterId: {
68
- udp: 'test',
68
+ udp: { result: 'reachable', latencyInMilliseconds: 10 },
69
+ tcp: { result: 'unreachable' },
70
+ isVideoMesh: false,
69
71
  },
70
72
  })
71
73
  );
@@ -78,7 +80,16 @@ describe('plugin-meetings/roap', () => {
78
80
  assert.deepEqual(res.localSdp, {
79
81
  reachability: {
80
82
  clusterId: {
81
- udp: 'test',
83
+ udp: {
84
+ reachable: 'true',
85
+ latencyInMilliseconds: '10',
86
+ },
87
+ tcp: {
88
+ reachable: 'false',
89
+ },
90
+ xtls: {
91
+ untested: 'true',
92
+ }
82
93
  },
83
94
  },
84
95
  });
@@ -148,7 +159,20 @@ describe('plugin-meetings/roap', () => {
148
159
  roapMessage: {
149
160
  seq: 'seq',
150
161
  },
151
- reachability: {clusterId: {udp: 'test'}},
162
+ reachability: {
163
+ clusterId: {
164
+ tcp: {
165
+ reachable: 'false',
166
+ },
167
+ udp: {
168
+ latencyInMilliseconds: '10',
169
+ reachable: 'true',
170
+ },
171
+ xtls: {
172
+ untested: 'true',
173
+ },
174
+ },
175
+ },
152
176
  });
153
177
  });
154
178