@webex/plugin-meetings 3.8.0-next.8 → 3.8.0-next.81

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 (171) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +70 -6
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/common/errors/webex-errors.js +12 -2
  5. package/dist/common/errors/webex-errors.js.map +1 -1
  6. package/dist/config.js +5 -1
  7. package/dist/config.js.map +1 -1
  8. package/dist/constants.js +20 -123
  9. package/dist/constants.js.map +1 -1
  10. package/dist/controls-options-manager/enums.js +2 -0
  11. package/dist/controls-options-manager/enums.js.map +1 -1
  12. package/dist/controls-options-manager/types.js.map +1 -1
  13. package/dist/controls-options-manager/util.js +52 -0
  14. package/dist/controls-options-manager/util.js.map +1 -1
  15. package/dist/interpretation/index.js +1 -1
  16. package/dist/interpretation/siLanguage.js +1 -1
  17. package/dist/locus-info/controlsUtils.js +28 -10
  18. package/dist/locus-info/controlsUtils.js.map +1 -1
  19. package/dist/locus-info/index.js +62 -12
  20. package/dist/locus-info/index.js.map +1 -1
  21. package/dist/locus-info/selfUtils.js +432 -418
  22. package/dist/locus-info/selfUtils.js.map +1 -1
  23. package/dist/media/index.js +17 -17
  24. package/dist/media/index.js.map +1 -1
  25. package/dist/media/properties.js +94 -6
  26. package/dist/media/properties.js.map +1 -1
  27. package/dist/meeting/brbState.js +6 -0
  28. package/dist/meeting/brbState.js.map +1 -1
  29. package/dist/meeting/in-meeting-actions.js +17 -1
  30. package/dist/meeting/in-meeting-actions.js.map +1 -1
  31. package/dist/meeting/index.js +570 -302
  32. package/dist/meeting/index.js.map +1 -1
  33. package/dist/meeting/locusMediaRequest.js +0 -17
  34. package/dist/meeting/locusMediaRequest.js.map +1 -1
  35. package/dist/meeting/muteState.js +0 -2
  36. package/dist/meeting/muteState.js.map +1 -1
  37. package/dist/meeting/request.js +30 -0
  38. package/dist/meeting/request.js.map +1 -1
  39. package/dist/meeting/request.type.js.map +1 -1
  40. package/dist/meeting/util.js +13 -2
  41. package/dist/meeting/util.js.map +1 -1
  42. package/dist/meeting-info/meeting-info-v2.js +373 -68
  43. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  44. package/dist/meeting-info/utilv2.js +5 -1
  45. package/dist/meeting-info/utilv2.js.map +1 -1
  46. package/dist/meetings/index.js +136 -1
  47. package/dist/meetings/index.js.map +1 -1
  48. package/dist/meetings/util.js +14 -0
  49. package/dist/meetings/util.js.map +1 -1
  50. package/dist/member/index.js +10 -0
  51. package/dist/member/index.js.map +1 -1
  52. package/dist/member/util.js +330 -353
  53. package/dist/member/util.js.map +1 -1
  54. package/dist/members/index.js +42 -0
  55. package/dist/members/index.js.map +1 -1
  56. package/dist/members/request.js +38 -0
  57. package/dist/members/request.js.map +1 -1
  58. package/dist/members/util.js +36 -1
  59. package/dist/members/util.js.map +1 -1
  60. package/dist/metrics/constants.js +9 -0
  61. package/dist/metrics/constants.js.map +1 -1
  62. package/dist/reachability/clusterReachability.js +63 -27
  63. package/dist/reachability/clusterReachability.js.map +1 -1
  64. package/dist/reachability/index.js +112 -47
  65. package/dist/reachability/index.js.map +1 -1
  66. package/dist/reachability/reachability.types.js +14 -0
  67. package/dist/reachability/reachability.types.js.map +1 -1
  68. package/dist/reachability/request.js +19 -3
  69. package/dist/reachability/request.js.map +1 -1
  70. package/dist/reconnection-manager/index.js +2 -2
  71. package/dist/reconnection-manager/index.js.map +1 -1
  72. package/dist/roap/index.js.map +1 -1
  73. package/dist/roap/turnDiscovery.js +45 -27
  74. package/dist/roap/turnDiscovery.js.map +1 -1
  75. package/dist/roap/types.js +17 -0
  76. package/dist/roap/types.js.map +1 -0
  77. package/dist/types/common/errors/webex-errors.d.ts +7 -1
  78. package/dist/types/config.d.ts +3 -0
  79. package/dist/types/constants.d.ts +13 -85
  80. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  81. package/dist/types/controls-options-manager/types.d.ts +7 -1
  82. package/dist/types/locus-info/index.d.ts +3 -3
  83. package/dist/types/locus-info/selfUtils.d.ts +216 -1
  84. package/dist/types/media/properties.d.ts +15 -0
  85. package/dist/types/meeting/in-meeting-actions.d.ts +16 -0
  86. package/dist/types/meeting/index.d.ts +43 -1
  87. package/dist/types/meeting/muteState.d.ts +0 -1
  88. package/dist/types/meeting/request.d.ts +12 -1
  89. package/dist/types/meeting/request.type.d.ts +6 -0
  90. package/dist/types/meeting/util.d.ts +3 -1
  91. package/dist/types/meeting-info/meeting-info-v2.d.ts +82 -1
  92. package/dist/types/meetings/index.d.ts +57 -0
  93. package/dist/types/member/index.d.ts +1 -0
  94. package/dist/types/member/util.d.ts +159 -1
  95. package/dist/types/members/index.d.ts +15 -0
  96. package/dist/types/members/request.d.ts +26 -0
  97. package/dist/types/members/util.d.ts +27 -0
  98. package/dist/types/metrics/constants.d.ts +9 -0
  99. package/dist/types/reachability/clusterReachability.d.ts +15 -7
  100. package/dist/types/reachability/index.d.ts +10 -1
  101. package/dist/types/reachability/reachability.types.d.ts +5 -0
  102. package/dist/types/roap/index.d.ts +3 -2
  103. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  104. package/dist/types/roap/types.d.ts +16 -0
  105. package/dist/webinar/index.js +1 -1
  106. package/package.json +24 -23
  107. package/src/breakouts/index.ts +69 -0
  108. package/src/common/errors/webex-errors.ts +8 -1
  109. package/src/config.ts +3 -0
  110. package/src/constants.ts +20 -90
  111. package/src/controls-options-manager/enums.ts +2 -0
  112. package/src/controls-options-manager/types.ts +11 -1
  113. package/src/controls-options-manager/util.ts +62 -0
  114. package/src/locus-info/controlsUtils.ts +44 -14
  115. package/src/locus-info/index.ts +56 -13
  116. package/src/locus-info/selfUtils.ts +496 -442
  117. package/src/media/index.ts +23 -21
  118. package/src/media/properties.ts +96 -0
  119. package/src/meeting/brbState.ts +7 -0
  120. package/src/meeting/in-meeting-actions.ts +32 -0
  121. package/src/meeting/index.ts +382 -93
  122. package/src/meeting/locusMediaRequest.ts +0 -18
  123. package/src/meeting/muteState.ts +0 -2
  124. package/src/meeting/request.ts +36 -1
  125. package/src/meeting/request.type.ts +7 -0
  126. package/src/meeting/util.ts +11 -2
  127. package/src/meeting-info/meeting-info-v2.ts +254 -8
  128. package/src/meeting-info/utilv2.ts +5 -0
  129. package/src/meetings/index.ts +148 -1
  130. package/src/meetings/util.ts +18 -0
  131. package/src/member/index.ts +13 -2
  132. package/src/member/util.ts +351 -348
  133. package/src/members/index.ts +47 -0
  134. package/src/members/request.ts +44 -0
  135. package/src/members/util.ts +43 -1
  136. package/src/metrics/constants.ts +9 -0
  137. package/src/reachability/clusterReachability.ts +73 -26
  138. package/src/reachability/index.ts +70 -1
  139. package/src/reachability/reachability.types.ts +6 -0
  140. package/src/reachability/request.ts +7 -0
  141. package/src/reconnection-manager/index.ts +2 -2
  142. package/src/roap/index.ts +3 -7
  143. package/src/roap/turnDiscovery.ts +34 -39
  144. package/src/roap/types.ts +23 -0
  145. package/test/unit/spec/breakouts/index.ts +167 -95
  146. package/test/unit/spec/controls-options-manager/util.js +120 -0
  147. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  148. package/test/unit/spec/locus-info/index.js +167 -73
  149. package/test/unit/spec/locus-info/selfUtils.js +98 -24
  150. package/test/unit/spec/media/index.ts +150 -18
  151. package/test/unit/spec/media/properties.ts +130 -0
  152. package/test/unit/spec/meeting/brbState.ts +19 -0
  153. package/test/unit/spec/meeting/in-meeting-actions.ts +19 -4
  154. package/test/unit/spec/meeting/index.js +557 -35
  155. package/test/unit/spec/meeting/locusMediaRequest.ts +0 -30
  156. package/test/unit/spec/meeting/muteState.js +0 -2
  157. package/test/unit/spec/meeting/request.js +32 -1
  158. package/test/unit/spec/meeting/utils.js +119 -18
  159. package/test/unit/spec/meeting-info/meetinginfov2.js +484 -114
  160. package/test/unit/spec/meeting-info/utilv2.js +19 -0
  161. package/test/unit/spec/meetings/index.js +146 -2
  162. package/test/unit/spec/member/index.js +7 -0
  163. package/test/unit/spec/member/util.js +24 -0
  164. package/test/unit/spec/members/index.js +140 -26
  165. package/test/unit/spec/members/request.js +68 -22
  166. package/test/unit/spec/members/utils.js +75 -0
  167. package/test/unit/spec/reachability/clusterReachability.ts +88 -56
  168. package/test/unit/spec/reachability/index.ts +101 -0
  169. package/test/unit/spec/reachability/request.js +47 -2
  170. package/test/unit/spec/reconnection-manager/index.js +4 -4
  171. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
@@ -9,7 +9,7 @@ import Meetings from '@webex/plugin-meetings';
9
9
  import MembersRequest from '@webex/plugin-meetings/src/members/request';
10
10
  import membersUtil from '@webex/plugin-meetings/src/members/util';
11
11
  import ParameterError from '@webex/plugin-meetings/src/common/errors/parameter';
12
- import { merge } from 'lodash';
12
+ import {merge} from 'lodash';
13
13
 
14
14
  const {assert} = chai;
15
15
 
@@ -65,10 +65,7 @@ describe('plugin-meetings', () => {
65
65
 
66
66
  const checkRequest = (expectedParams) => {
67
67
  assert.calledOnceWithExactly(locusDeltaRequestSpy, expectedParams);
68
- assert.calledOnceWithExactly(
69
- membersRequest.request,
70
- merge(expectedParams, {body: {sequence}})
71
- );
68
+ assert.calledOnceWithExactly(membersRequest.request, merge(expectedParams, {body: {sequence}}));
72
69
  };
73
70
 
74
71
  describe('members request library', () => {
@@ -98,8 +95,8 @@ describe('plugin-meetings', () => {
98
95
  },
99
96
  device: {
100
97
  url,
101
- }
102
- }
98
+ },
99
+ },
103
100
  });
104
101
  });
105
102
  });
@@ -120,9 +117,9 @@ describe('plugin-meetings', () => {
120
117
  uri: url1,
121
118
  body: {
122
119
  alertIfActive: undefined,
123
- invitees: [{address: '+18578675309'}]
124
- }
125
- })
120
+ invitees: [{address: '+18578675309'}],
121
+ },
122
+ });
126
123
  });
127
124
  });
128
125
 
@@ -133,16 +130,16 @@ describe('plugin-meetings', () => {
133
130
  memberIds: ['1', '2'],
134
131
  };
135
132
 
136
- await membersRequest.admitMember(options)
133
+ await membersRequest.admitMember(options);
137
134
 
138
135
  checkRequest({
139
136
  method: 'PUT',
140
137
  uri: 'https://example.com/12345/controls',
141
138
  body: {
142
139
  admit: {
143
- participantIds: options.memberIds
144
- }
145
- }
140
+ participantIds: options.memberIds,
141
+ },
142
+ },
146
143
  });
147
144
  });
148
145
  });
@@ -160,7 +157,7 @@ describe('plugin-meetings', () => {
160
157
  method: 'PUT',
161
158
  uri: 'https://example.com/12345/participant/member1/leave',
162
159
  body: {
163
- reason: undefined
160
+ reason: undefined,
164
161
  },
165
162
  });
166
163
  });
@@ -224,6 +221,29 @@ describe('plugin-meetings', () => {
224
221
  });
225
222
  });
226
223
 
224
+ describe('#cancelSIPInvite', () => {
225
+ const memberId = uuid.v4();
226
+ it('sends a PUT to the locus endpoint', async () => {
227
+ const options = {
228
+ invitee: {
229
+ memberId,
230
+ },
231
+ locusUrl: url1,
232
+ };
233
+
234
+ await membersRequest.cancelSIPInvite(options);
235
+
236
+ checkRequest({
237
+ method: 'PUT',
238
+ uri: url1,
239
+ body: {
240
+ actionType: 'REMOVE',
241
+ invitees: [{address: memberId}],
242
+ },
243
+ });
244
+ });
245
+ });
246
+
227
247
  describe('#assignRolesMember', () => {
228
248
  it('sends a assignRolesMember PATCH to the locus endpoint', async () => {
229
249
  const locusUrl = url1;
@@ -247,9 +267,9 @@ describe('plugin-meetings', () => {
247
267
  uri: `${locusUrl}/participant/${memberId}/controls`,
248
268
  body: {
249
269
  role: {
250
- roles
251
- }
252
- }
270
+ roles,
271
+ },
272
+ },
253
273
  });
254
274
  });
255
275
  });
@@ -272,9 +292,9 @@ describe('plugin-meetings', () => {
272
292
  uri: `${locusUrl}/participant/${memberId}/controls`,
273
293
  body: {
274
294
  hand: {
275
- raised: true
276
- }
277
- }
295
+ raised: true,
296
+ },
297
+ },
278
298
  });
279
299
  });
280
300
  });
@@ -406,7 +426,33 @@ describe('plugin-meetings', () => {
406
426
  body: {
407
427
  aliasValue,
408
428
  requestingParticipantId,
409
- }
429
+ },
430
+ });
431
+ });
432
+ });
433
+
434
+ describe('#moveToLobby', () => {
435
+ it('sends a moveToLobbyMember PATCH to the locus endpoint', async () => {
436
+ const locusUrl = url1;
437
+ const memberId = 'test1';
438
+ const options = {
439
+ locusUrl: locusUrl,
440
+ memberId,
441
+ };
442
+ const body = {
443
+ moveToLobby: {participantIds: [memberId]},
444
+ };
445
+
446
+ const getRequestParamsSpy = sandbox.spy(membersUtil, 'getMoveMemberToLobbyRequestParams');
447
+
448
+ await membersRequest.moveToLobbyMember(options, body);
449
+
450
+ assert.calledOnceWithExactly(getRequestParamsSpy, options, body);
451
+
452
+ checkRequest({
453
+ method: 'PATCH',
454
+ uri: `${locusUrl}/participant/${memberId}/controls`,
455
+ body: {moveToLobby: {participantIds: [memberId]}},
410
456
  });
411
457
  });
412
458
  });
@@ -357,5 +357,80 @@ describe('plugin-meetings', () => {
357
357
  });
358
358
  });
359
359
  });
360
+
361
+ describe('#getMoveMemberToLobbyRequestBody', () => {
362
+ it('returns the correct options', () => {
363
+ const memberId = 'test1';
364
+ assert.deepEqual(MembersUtil.getMoveMemberToLobbyRequestBody(memberId), {
365
+ moveToLobby: {
366
+ participantIds: [memberId],
367
+ },
368
+ });
369
+ });
370
+ });
371
+
372
+ describe('#getMoveMemberToLobbyRequestParams', () => {
373
+ it('returns the correct params', () => {
374
+ const locusUrl = 'TestLocusUrl';
375
+ const memberId = 'test1';
376
+ const options = {
377
+ locusUrl: locusUrl,
378
+ memberId,
379
+ };
380
+ const body = {
381
+ moveToLobby: {participantIds: [memberId]},
382
+ };
383
+
384
+ const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;
385
+
386
+ assert.deepEqual(MembersUtil.getMoveMemberToLobbyRequestParams(options, body), {
387
+ method: HTTP_VERBS.PATCH,
388
+ uri,
389
+ body,
390
+ });
391
+ });
392
+ });
393
+
394
+ describe('#cancelSIPInviteOptions', () => {
395
+ it('returns the correct options', () => {
396
+ const locusUrl = 'TestLocusUrl';
397
+ const memberId = 'test';
398
+ const invitee = {memberId};
399
+
400
+ assert.deepEqual(
401
+ MembersUtil.cancelSIPInviteOptions(
402
+ invitee,
403
+ locusUrl
404
+ ),
405
+ {
406
+ invitee,
407
+ locusUrl,
408
+ }
409
+ );
410
+ });
411
+ });
412
+
413
+ describe('#generateCancelSIPInviteRequestParams', () => {
414
+ it('returns the correct params', () => {
415
+ const locusUrl = 'TestLocusUrl';
416
+ const memberId = 'test';
417
+ const options = {
418
+ locusUrl,
419
+ invitee: {memberId}
420
+ };
421
+ const body = {
422
+ actionType: 'REMOVE',
423
+ invitees: [{address: options.invitee.memberId}],
424
+ };
425
+
426
+ const uri = options.locusUrl;
427
+
428
+ assert.deepEqual(MembersUtil.generateCancelSIPInviteRequestParams(options), {
429
+ method: HTTP_VERBS.PUT,
430
+ uri,
431
+ body,
432
+ });
433
+ });
434
+ });
360
435
  });
361
436
  });
@@ -9,7 +9,9 @@ import {
9
9
  ResultEventData,
10
10
  Events,
11
11
  ClientMediaIpsUpdatedEventData,
12
+ NatTypeUpdatedEventData,
12
13
  } from '@webex/plugin-meetings/src/reachability/clusterReachability'; // replace with actual path
14
+ import { NatType } from 'packages/@webex/plugin-meetings/dist/reachability/reachability.types';
13
15
 
14
16
  describe('ClusterReachability', () => {
15
17
  let previousRTCPeerConnection;
@@ -17,15 +19,17 @@ describe('ClusterReachability', () => {
17
19
  let fakePeerConnection;
18
20
  let gatherIceCandidatesSpy;
19
21
 
20
- const emittedEvents: Record<Events, (ResultEventData | ClientMediaIpsUpdatedEventData)[]> = {
22
+ const emittedEvents: Record<Events, (ResultEventData | ClientMediaIpsUpdatedEventData | NatTypeUpdatedEventData)[]> = {
21
23
  [Events.resultReady]: [],
22
24
  [Events.clientMediaIpsUpdated]: [],
25
+ [Events.natTypeUpdated]: [],
23
26
  };
24
27
  const FAKE_OFFER = {type: 'offer', sdp: 'fake sdp'};
25
28
 
26
29
  const resetEmittedEvents = () => {
27
30
  emittedEvents[Events.resultReady].length = 0;
28
31
  emittedEvents[Events.clientMediaIpsUpdated].length = 0;
32
+ emittedEvents[Events.natTypeUpdated].length = 0;
29
33
  };
30
34
  beforeEach(() => {
31
35
  fakePeerConnection = {
@@ -56,6 +60,10 @@ describe('ClusterReachability', () => {
56
60
  clusterReachability.on(Events.clientMediaIpsUpdated, (data: ClientMediaIpsUpdatedEventData) => {
57
61
  emittedEvents[Events.clientMediaIpsUpdated].push(data);
58
62
  });
63
+
64
+ clusterReachability.on(Events.natTypeUpdated, (data: NatTypeUpdatedEventData) => {
65
+ emittedEvents[Events.natTypeUpdated].push(data);
66
+ });
59
67
  });
60
68
 
61
69
  afterEach(() => {
@@ -166,59 +174,6 @@ describe('ClusterReachability', () => {
166
174
  assert.deepEqual(emittedEvents[Events.clientMediaIpsUpdated], []);
167
175
  });
168
176
 
169
- it('resolves and has correct result as soon as it finds that all udp, tcp and tls are reachable', async () => {
170
- const promise = clusterReachability.start();
171
-
172
- await clock.tickAsync(100);
173
- fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp'}});
174
-
175
- // check the right events were emitted
176
- assert.equal(emittedEvents[Events.resultReady].length, 1);
177
- assert.deepEqual(emittedEvents[Events.resultReady][0], {
178
- protocol: 'udp',
179
- result: 'reachable',
180
- latencyInMilliseconds: 100,
181
- clientMediaIPs: ['somePublicIp'],
182
- });
183
-
184
- // clientMediaIpsUpdated shouldn't be emitted, because the IP is already passed in the resultReady event
185
- assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
186
-
187
- await clock.tickAsync(100);
188
- fakePeerConnection.onicecandidate({candidate: {type: 'relay', address: 'someTurnRelayIp'}});
189
-
190
- // check the right event was emitted
191
- assert.equal(emittedEvents[Events.resultReady].length, 2);
192
- assert.deepEqual(emittedEvents[Events.resultReady][1], {
193
- protocol: 'tcp',
194
- result: 'reachable',
195
- latencyInMilliseconds: 200,
196
- });
197
- assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
198
-
199
- await clock.tickAsync(100);
200
- fakePeerConnection.onicecandidate({
201
- candidate: {type: 'relay', address: 'someTurnRelayIp', port: 443},
202
- });
203
-
204
- // check the right event was emitted
205
- assert.equal(emittedEvents[Events.resultReady].length, 3);
206
- assert.deepEqual(emittedEvents[Events.resultReady][2], {
207
- protocol: 'xtls',
208
- result: 'reachable',
209
- latencyInMilliseconds: 300,
210
- });
211
- assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
212
-
213
- await promise;
214
-
215
- assert.deepEqual(clusterReachability.getResult(), {
216
- udp: {result: 'reachable', latencyInMilliseconds: 100, clientMediaIPs: ['somePublicIp']},
217
- tcp: {result: 'reachable', latencyInMilliseconds: 200},
218
- xtls: {result: 'reachable', latencyInMilliseconds: 300},
219
- });
220
- });
221
-
222
177
  it('resolves and returns correct results when aborted before it gets any candidates', async () => {
223
178
  const promise = clusterReachability.start();
224
179
 
@@ -267,7 +222,7 @@ describe('ClusterReachability', () => {
267
222
 
268
223
  await testUtils.flushPromises();
269
224
 
270
- fakePeerConnection.iceConnectionState = 'complete';
225
+ fakePeerConnection.iceGatheringState = 'complete';
271
226
  fakePeerConnection.onicegatheringstatechange();
272
227
  await promise;
273
228
 
@@ -285,7 +240,7 @@ describe('ClusterReachability', () => {
285
240
  await clock.tickAsync(30);
286
241
  fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp1'}});
287
242
 
288
- fakePeerConnection.iceConnectionState = 'complete';
243
+ fakePeerConnection.iceGatheringState = 'complete';
289
244
  fakePeerConnection.onicegatheringstatechange();
290
245
  await promise;
291
246
 
@@ -428,6 +383,9 @@ describe('ClusterReachability', () => {
428
383
  candidate: {type: 'relay', address: 'someTurnRelayIp', port: 443},
429
384
  });
430
385
 
386
+ fakePeerConnection.iceGatheringState = 'complete';
387
+ fakePeerConnection.onicegatheringstatechange();
388
+
431
389
  await promise;
432
390
 
433
391
  assert.deepEqual(clusterReachability.getResult(), {
@@ -440,5 +398,79 @@ describe('ClusterReachability', () => {
440
398
  xtls: {result: 'reachable', latencyInMilliseconds: 40},
441
399
  });
442
400
  });
401
+
402
+ it('determines correctly if symmetric-nat is detected', async () => {
403
+ const promise = clusterReachability.start();
404
+
405
+ // generate candidates with duplicate addresses
406
+ await clock.tickAsync(10);
407
+ fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp1', relatedPort: 3478, port: 1000}});
408
+
409
+ // check events emitted: there shouldn't be any natTypeUpdated emitted
410
+ assert.equal(emittedEvents[Events.natTypeUpdated].length, 0);
411
+
412
+ await clock.tickAsync(10);
413
+ fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp1', relatedPort: 3478, port: 2000}});
414
+
415
+ // should emit natTypeUpdated event
416
+ assert.equal(emittedEvents[Events.natTypeUpdated].length, 1);
417
+ assert.deepEqual(emittedEvents[Events.natTypeUpdated][0], {
418
+ natType: 'symmetric-nat',
419
+ });
420
+
421
+ // send also a relay candidate so that the reachability check finishes
422
+ fakePeerConnection.onicecandidate({candidate: {type: 'relay', address: 'someTurnRelayIp'}});
423
+ fakePeerConnection.onicecandidate({
424
+ candidate: {type: 'relay', address: 'someTurnRelayIp', port: 443},
425
+ });
426
+
427
+ fakePeerConnection.iceGatheringState = 'complete';
428
+ fakePeerConnection.onicegatheringstatechange();
429
+ await clock.tickAsync(10);
430
+
431
+ await promise;
432
+
433
+ assert.deepEqual(clusterReachability.getResult(), {
434
+ udp: {
435
+ result: 'reachable',
436
+ latencyInMilliseconds: 10,
437
+ clientMediaIPs: ['somePublicIp1'],
438
+ },
439
+ tcp: {result: 'reachable', latencyInMilliseconds: 20},
440
+ xtls: {result: 'reachable', latencyInMilliseconds: 20},
441
+ });
442
+ });
443
+
444
+ it('should gather correctly reached subnets', async () => {
445
+ const promise = clusterReachability.start();
446
+
447
+ await clock.tickAsync(10);
448
+ fakePeerConnection.onicecandidate({candidate: {type: 'srflx', url: 'stun:1.2.3.4:5004'}});
449
+ fakePeerConnection.onicecandidate({candidate: {type: 'srflx', url: 'stun:4.3.2.1:5004'}});
450
+ fakePeerConnection.onicecandidate({candidate: {type: 'relay', address: 'someTurnRelayIp'}});
451
+
452
+ clusterReachability.abort();
453
+ await promise;
454
+
455
+ assert.deepEqual(Array.from(clusterReachability.reachedSubnets), [
456
+ '1.2.3.4',
457
+ '4.3.2.1',
458
+ 'someTurnRelayIp'
459
+ ]);
460
+ });
461
+
462
+ it('should store only unique subnet address', async () => {
463
+ const promise = clusterReachability.start();
464
+
465
+ await clock.tickAsync(10);
466
+ fakePeerConnection.onicecandidate({candidate: {type: 'srflx', url: 'stun:1.2.3.4:5004'}});
467
+ fakePeerConnection.onicecandidate({candidate: {type: 'srflx', url: 'stun:1.2.3.4:9000'}});
468
+ fakePeerConnection.onicecandidate({candidate: {type: 'relay', address: '1.2.3.4'}});
469
+
470
+ clusterReachability.abort();
471
+ await promise;
472
+
473
+ assert.deepEqual(Array.from(clusterReachability.reachedSubnets), ['1.2.3.4']);
474
+ });
443
475
  });
444
476
  });
@@ -538,6 +538,14 @@ describe('gatherReachability', () => {
538
538
  assert.equal(storedResultForJoinCookie, JSON.stringify(expectedJoinCookie));
539
539
  };
540
540
 
541
+ it('rejects if reachability is disabled in config', async () => {
542
+ webex.config.meetings.enableReachabilityChecks = false;
543
+
544
+ const reachability = new Reachability(webex);
545
+
546
+ await assert.isRejected(reachability.gatherReachability('test'), 'enableReachabilityChecks is disabled in config');
547
+ });
548
+
541
549
  [
542
550
  // ========================================================================
543
551
  {
@@ -1947,6 +1955,7 @@ describe('gatherReachability', () => {
1947
1955
  receivedEvents[event] = receivedEvents[event] + 1 || 1;
1948
1956
  });
1949
1957
  };
1958
+
1950
1959
  it('works as expected', async () => {
1951
1960
  setListener('reachability:stopped');
1952
1961
  setListener('reachability:done');
@@ -2008,6 +2017,59 @@ describe('gatherReachability', () => {
2008
2017
  assert.equal(receivedEvents['reachability:done'], undefined);
2009
2018
  assert.equal(receivedEvents['reachability:firstResultAvailable'], undefined);
2010
2019
  });
2020
+
2021
+ it('does not fallback when no clusters were reached and min clusters were specified', async () => {
2022
+ setListener('reachability:stopped');
2023
+ setListener('reachability:done');
2024
+ setListener('reachability:firstResultAvailable');
2025
+
2026
+ const mockGetClustersResult = {
2027
+ discoveryOptions: {
2028
+ ['early-call-min-clusters']: 1,
2029
+ },
2030
+ clusters: {
2031
+ clusterA: {
2032
+ udp: [],
2033
+ tcp: [],
2034
+ xtls: [],
2035
+ isVideoMesh: false,
2036
+ },
2037
+ clusterB: {
2038
+ udp: [],
2039
+ tcp: [],
2040
+ xtls: [],
2041
+ isVideoMesh: false,
2042
+ },
2043
+ },
2044
+ joinCookie: {id: 'id'},
2045
+ };
2046
+
2047
+ reachability.reachabilityRequest.getClusters = sinon.stub().returns(mockGetClustersResult);
2048
+
2049
+ const gatherReachabilityFallbackSpy = sinon.spy(reachability, 'gatherReachabilityFallback');
2050
+
2051
+ const resultPromise = reachability.gatherReachability('test');
2052
+
2053
+ await testUtils.flushPromises();
2054
+
2055
+ reachability.stopReachability();
2056
+
2057
+ await resultPromise;
2058
+
2059
+ // simulate a lot of time passing to check that all timers were stopped and nothing else happens
2060
+ clock.tick(99000);
2061
+
2062
+ assert.calledOnceWithExactly(mockClusterReachabilityInstances['clusterA'].abort);
2063
+ assert.calledOnceWithExactly(mockClusterReachabilityInstances['clusterB'].abort);
2064
+
2065
+ assert.calledOnceWithExactly(sendMetricSpy, true);
2066
+
2067
+ assert.equal(receivedEvents['reachability:stopped'], 1);
2068
+ assert.equal(receivedEvents['reachability:done'], undefined);
2069
+ assert.equal(receivedEvents['reachability:firstResultAvailable'], undefined);
2070
+
2071
+ assert.notCalled(gatherReachabilityFallbackSpy);
2072
+ });
2011
2073
  });
2012
2074
  });
2013
2075
 
@@ -2148,6 +2210,7 @@ describe('getReachabilityMetrics', () => {
2148
2210
  reachability_vmn_tcp_failed: 0,
2149
2211
  reachability_vmn_xtls_success: 0,
2150
2212
  reachability_vmn_xtls_failed: 0,
2213
+ natType: 'unknown'
2151
2214
  });
2152
2215
  });
2153
2216
 
@@ -2215,6 +2278,7 @@ describe('getReachabilityMetrics', () => {
2215
2278
  reachability_vmn_tcp_failed: 1,
2216
2279
  reachability_vmn_xtls_success: 0,
2217
2280
  reachability_vmn_xtls_failed: 0,
2281
+ natType: 'unknown'
2218
2282
  }
2219
2283
  );
2220
2284
  });
@@ -2276,6 +2340,7 @@ describe('getReachabilityMetrics', () => {
2276
2340
  reachability_vmn_tcp_failed: 0,
2277
2341
  reachability_vmn_xtls_success: 0,
2278
2342
  reachability_vmn_xtls_failed: 0,
2343
+ natType: 'unknown'
2279
2344
  }
2280
2345
  );
2281
2346
  });
@@ -2337,6 +2402,7 @@ describe('getReachabilityMetrics', () => {
2337
2402
  reachability_vmn_tcp_failed: 3,
2338
2403
  reachability_vmn_xtls_success: 1,
2339
2404
  reachability_vmn_xtls_failed: 1,
2405
+ natType: 'unknown'
2340
2406
  }
2341
2407
  );
2342
2408
  });
@@ -2674,3 +2740,38 @@ describe('sendMetric', () => {
2674
2740
  });
2675
2741
  });
2676
2742
  });
2743
+
2744
+ describe('isSubnetReachable', () => {
2745
+ let webex;
2746
+ let reachability;
2747
+
2748
+ beforeEach(() => {
2749
+ webex = new MockWebex();
2750
+ reachability = new TestReachability(webex);
2751
+
2752
+ reachability.setFakeClusterReachability({
2753
+ cluster1: {
2754
+ reachedSubnets: new Set(['1.2.3.4', '2.3.4.5']),
2755
+ },
2756
+ cluster2: {
2757
+ reachedSubnets: new Set(['3.4.5.6', '4.5.6.7']),
2758
+ },
2759
+ });
2760
+ });
2761
+
2762
+ afterEach(() => {
2763
+ sinon.restore();
2764
+ });
2765
+
2766
+ it('returns true if the subnet is reachable', () => {
2767
+ assert(reachability.isSubnetReachable('1.2.3.4'));
2768
+ });
2769
+
2770
+ it(`returns false if the subnet is unreachable`, () => {
2771
+ assert(!reachability.isSubnetReachable('11.2.3.4'));
2772
+ });
2773
+
2774
+ it('returns null if the subnet is not provided', () => {
2775
+ assert.isNull(reachability.isSubnetReachable(undefined));
2776
+ });
2777
+ });
@@ -12,6 +12,9 @@ describe('plugin-meetings/reachability', () => {
12
12
  let reachabilityRequest;
13
13
  let webex;
14
14
 
15
+ let appType;
16
+ let appVersion;
17
+
15
18
  beforeEach(() => {
16
19
  webex = new MockWebex({
17
20
  children: {
@@ -20,6 +23,14 @@ describe('plugin-meetings/reachability', () => {
20
23
  },
21
24
  });
22
25
 
26
+ webex.config.support = {
27
+ 'appType': 'NetworkChecker',
28
+ 'appVersion': '43.3.0.1',
29
+ }
30
+
31
+ appType = webex?.config?.support?.appType;
32
+ appVersion = webex?.config?.support?.appVersion;
33
+
23
34
  webex.meetings.clientRegion = {
24
35
  countryCode: 'US',
25
36
  regionCode: 'WEST-COAST',
@@ -56,7 +67,9 @@ describe('plugin-meetings/reachability', () => {
56
67
 
57
68
  previousReport = {
58
69
  id: 'fake previous report',
59
- }
70
+ };
71
+
72
+
60
73
  });
61
74
 
62
75
  afterEach(() => {
@@ -79,6 +92,7 @@ describe('plugin-meetings/reachability', () => {
79
92
  'report-version': 1,
80
93
  'early-call-min-clusters': true,
81
94
  },
95
+ 'client-environment': { components: { [appType]: appVersion } },
82
96
  'previous-report': previousReport,
83
97
  trigger: 'startup',
84
98
  },
@@ -105,6 +119,7 @@ describe('plugin-meetings/reachability', () => {
105
119
  'report-version': 1,
106
120
  'early-call-min-clusters': true,
107
121
  },
122
+ 'client-environment': { components: { [appType]: appVersion } },
108
123
  'previous-report': previousReport,
109
124
  trigger: 'early-call/no-min-reached',
110
125
  },
@@ -114,5 +129,35 @@ describe('plugin-meetings/reachability', () => {
114
129
  assert.deepEqual(res.joinCookie, {anycastEntryPoint: "aws-eu-west-1"})
115
130
  assert.notCalled(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency);
116
131
  });
132
+
133
+ it('sends a POST request with the correct params when appVersion is undefined', async () => {
134
+ // Setting appType & appVersion to undefined
135
+ webex.config.support.appType = undefined;
136
+ webex.config.support.appVersion = undefined;
137
+
138
+ const res = await reachabilityRequest.getClusters('startup', IP_VERSION.only_ipv4, previousReport);
139
+ const requestParams = webex.request.getCall(0).args[0];
140
+
141
+ assert.deepEqual(requestParams, {
142
+ method: 'POST',
143
+ resource: `clusters`,
144
+ api: 'calliopeDiscovery',
145
+ shouldRefreshAccessToken: false,
146
+ timeout: 3000,
147
+ body: {
148
+ ipver: IP_VERSION.only_ipv4,
149
+ 'supported-options': {
150
+ 'report-version': 1,
151
+ 'early-call-min-clusters': true,
152
+ },
153
+ 'previous-report': previousReport,
154
+ trigger: 'startup',
155
+ },
156
+ });
157
+
158
+ assert.deepEqual(res.clusters.clusterId, {udp: "testUDP", isVideoMesh: true});
159
+ assert.deepEqual(res.joinCookie, {anycastEntryPoint: "aws-eu-west-1"});
160
+ assert.calledOnceWithExactly(webex.internal.newMetrics.callDiagnosticLatencies.measureLatency, sinon.match.func, 'internal.get.cluster.time');
161
+ });
117
162
  });
118
- });
163
+ });