@webex/plugin-meetings 3.3.1 → 3.4.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 (126) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +7 -2
  3. package/dist/breakouts/index.js.map +1 -1
  4. package/dist/constants.js +11 -4
  5. package/dist/constants.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/locus-info/selfUtils.js +0 -5
  9. package/dist/locus-info/selfUtils.js.map +1 -1
  10. package/dist/media/MediaConnectionAwaiter.js +70 -15
  11. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  12. package/dist/media/index.js +12 -0
  13. package/dist/media/index.js.map +1 -1
  14. package/dist/meeting/connectionStateHandler.js +67 -0
  15. package/dist/meeting/connectionStateHandler.js.map +1 -0
  16. package/dist/meeting/index.js +552 -357
  17. package/dist/meeting/index.js.map +1 -1
  18. package/dist/meeting/locusMediaRequest.js +7 -0
  19. package/dist/meeting/locusMediaRequest.js.map +1 -1
  20. package/dist/meeting/muteState.js +6 -1
  21. package/dist/meeting/muteState.js.map +1 -1
  22. package/dist/meeting/util.js +1 -0
  23. package/dist/meeting/util.js.map +1 -1
  24. package/dist/meeting-info/index.js +4 -4
  25. package/dist/meeting-info/index.js.map +1 -1
  26. package/dist/meeting-info/meeting-info-v2.js +2 -2
  27. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  28. package/dist/meeting-info/util.js +17 -17
  29. package/dist/meeting-info/util.js.map +1 -1
  30. package/dist/meeting-info/utilv2.js +16 -16
  31. package/dist/meeting-info/utilv2.js.map +1 -1
  32. package/dist/meetings/collection.js +1 -1
  33. package/dist/meetings/collection.js.map +1 -1
  34. package/dist/meetings/index.js +37 -33
  35. package/dist/meetings/index.js.map +1 -1
  36. package/dist/meetings/meetings.types.js +8 -0
  37. package/dist/meetings/meetings.types.js.map +1 -1
  38. package/dist/meetings/util.js +3 -2
  39. package/dist/meetings/util.js.map +1 -1
  40. package/dist/metrics/constants.js +2 -1
  41. package/dist/metrics/constants.js.map +1 -1
  42. package/dist/metrics/index.js +57 -0
  43. package/dist/metrics/index.js.map +1 -1
  44. package/dist/personal-meeting-room/index.js +1 -1
  45. package/dist/personal-meeting-room/index.js.map +1 -1
  46. package/dist/reachability/clusterReachability.js +108 -53
  47. package/dist/reachability/clusterReachability.js.map +1 -1
  48. package/dist/reachability/index.js +415 -56
  49. package/dist/reachability/index.js.map +1 -1
  50. package/dist/types/constants.d.ts +11 -3
  51. package/dist/types/media/MediaConnectionAwaiter.d.ts +24 -4
  52. package/dist/types/meeting/connectionStateHandler.d.ts +30 -0
  53. package/dist/types/meeting/index.d.ts +27 -7
  54. package/dist/types/meeting/locusMediaRequest.d.ts +2 -0
  55. package/dist/types/meeting-info/index.d.ts +3 -2
  56. package/dist/types/meeting-info/meeting-info-v2.d.ts +3 -2
  57. package/dist/types/meeting-info/util.d.ts +5 -4
  58. package/dist/types/meeting-info/utilv2.d.ts +3 -2
  59. package/dist/types/meetings/collection.d.ts +3 -2
  60. package/dist/types/meetings/index.d.ts +4 -3
  61. package/dist/types/meetings/meetings.types.d.ts +9 -0
  62. package/dist/types/metrics/constants.d.ts +1 -0
  63. package/dist/types/metrics/index.d.ts +15 -0
  64. package/dist/types/reachability/clusterReachability.d.ts +31 -3
  65. package/dist/types/reachability/index.d.ts +93 -2
  66. package/dist/webinar/index.js +1 -1
  67. package/package.json +23 -23
  68. package/src/breakouts/index.ts +7 -1
  69. package/src/constants.ts +13 -17
  70. package/src/locus-info/selfUtils.ts +0 -5
  71. package/src/media/MediaConnectionAwaiter.ts +89 -14
  72. package/src/media/index.ts +13 -0
  73. package/src/meeting/connectionStateHandler.ts +65 -0
  74. package/src/meeting/index.ts +526 -292
  75. package/src/meeting/locusMediaRequest.ts +5 -0
  76. package/src/meeting/muteState.ts +6 -1
  77. package/src/meeting/util.ts +1 -0
  78. package/src/meeting-info/index.ts +9 -6
  79. package/src/meeting-info/meeting-info-v2.ts +4 -4
  80. package/src/meeting-info/util.ts +23 -28
  81. package/src/meeting-info/utilv2.ts +18 -24
  82. package/src/meetings/collection.ts +3 -3
  83. package/src/meetings/index.ts +39 -40
  84. package/src/meetings/meetings.types.ts +11 -0
  85. package/src/meetings/util.ts +5 -4
  86. package/src/metrics/constants.ts +1 -0
  87. package/src/metrics/index.ts +44 -0
  88. package/src/personal-meeting-room/index.ts +2 -2
  89. package/src/reachability/clusterReachability.ts +86 -25
  90. package/src/reachability/index.ts +316 -27
  91. package/test/unit/spec/breakouts/index.ts +51 -32
  92. package/test/unit/spec/locus-info/selfUtils.js +25 -23
  93. package/test/unit/spec/media/MediaConnectionAwaiter.ts +131 -32
  94. package/test/unit/spec/media/index.ts +42 -27
  95. package/test/unit/spec/meeting/connectionStateHandler.ts +102 -0
  96. package/test/unit/spec/meeting/index.js +758 -179
  97. package/test/unit/spec/meeting/locusMediaRequest.ts +7 -0
  98. package/test/unit/spec/meeting/muteState.js +24 -0
  99. package/test/unit/spec/meeting-info/index.js +4 -4
  100. package/test/unit/spec/meeting-info/meetinginfov2.js +24 -28
  101. package/test/unit/spec/meeting-info/request.js +2 -2
  102. package/test/unit/spec/meeting-info/utilv2.js +41 -49
  103. package/test/unit/spec/meetings/index.js +14 -0
  104. package/test/unit/spec/metrics/index.js +126 -0
  105. package/test/unit/spec/multistream/mediaRequestManager.ts +2 -2
  106. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +2 -2
  107. package/test/unit/spec/reachability/clusterReachability.ts +116 -22
  108. package/test/unit/spec/reachability/index.ts +1153 -84
  109. package/test/unit/spec/rtcMetrics/index.ts +1 -0
  110. package/dist/mediaQualityMetrics/config.js +0 -321
  111. package/dist/mediaQualityMetrics/config.js.map +0 -1
  112. package/dist/statsAnalyzer/global.js +0 -44
  113. package/dist/statsAnalyzer/global.js.map +0 -1
  114. package/dist/statsAnalyzer/index.js +0 -1072
  115. package/dist/statsAnalyzer/index.js.map +0 -1
  116. package/dist/statsAnalyzer/mqaUtil.js +0 -368
  117. package/dist/statsAnalyzer/mqaUtil.js.map +0 -1
  118. package/dist/types/mediaQualityMetrics/config.d.ts +0 -247
  119. package/dist/types/statsAnalyzer/global.d.ts +0 -36
  120. package/dist/types/statsAnalyzer/index.d.ts +0 -217
  121. package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -48
  122. package/src/mediaQualityMetrics/config.ts +0 -255
  123. package/src/statsAnalyzer/global.ts +0 -37
  124. package/src/statsAnalyzer/index.ts +0 -1318
  125. package/src/statsAnalyzer/mqaUtil.ts +0 -463
  126. package/test/unit/spec/stats-analyzer/index.js +0 -1819
@@ -58,4 +58,130 @@ describe('Meeting metrics', () => {
58
58
  });
59
59
  });
60
60
  });
61
+
62
+ describe('prepareMetricFields', () => {
63
+ it('handles empty objects correctly', () => {
64
+ const result = metrics.prepareMetricFields({});
65
+
66
+ assert.deepEqual(result, {});
67
+ });
68
+
69
+ it('handles literal values correctly', () => {
70
+ assert.deepEqual(metrics.prepareMetricFields('any string'), {value: 'any string'});
71
+ assert.deepEqual(metrics.prepareMetricFields(999), {value: 999});
72
+ assert.deepEqual(metrics.prepareMetricFields(null), {value: null});
73
+ assert.deepEqual(metrics.prepareMetricFields(true), {value: true});
74
+ assert.deepEqual(metrics.prepareMetricFields(false), {value: false});
75
+ });
76
+
77
+ it('handles simple objects correctly', () => {
78
+ const result = metrics.prepareMetricFields({
79
+ someStringProp: 'some string',
80
+ numberProp: 100,
81
+ booleanPropFalse: false,
82
+ booleanPropTrue: true,
83
+ });
84
+
85
+ assert.deepEqual(result, {
86
+ someStringProp: 'some string',
87
+ numberProp: 100,
88
+ booleanPropFalse: false,
89
+ booleanPropTrue: true,
90
+ });
91
+ });
92
+
93
+ it('handles nested objects correctly', () => {
94
+ const result = metrics.prepareMetricFields({
95
+ someStringProp: 'some string',
96
+ nestedObject: {
97
+ stringProp: 'another string',
98
+ numberProp: 1234,
99
+ deepObject: {
100
+ oneMoreString: 'deep nested string',
101
+ someBoolean: true,
102
+ oneMoreNumber: 10,
103
+ },
104
+ },
105
+ });
106
+
107
+ assert.deepEqual(result, {
108
+ someStringProp: 'some string',
109
+ nestedObject_stringProp: 'another string',
110
+ nestedObject_numberProp: 1234,
111
+ nestedObject_deepObject_oneMoreString: 'deep nested string',
112
+ nestedObject_deepObject_someBoolean: true,
113
+ nestedObject_deepObject_oneMoreNumber: 10,
114
+ });
115
+ });
116
+
117
+ it('handles arrays correctly', () => {
118
+ const result = metrics.prepareMetricFields(['a string', 10, true, false, {id: 'object id'}]);
119
+
120
+ assert.deepEqual(result, {
121
+ 0: 'a string',
122
+ 1: 10,
123
+ 2: true,
124
+ 3: false,
125
+ '4_id': 'object id',
126
+ });
127
+ });
128
+
129
+ it('handles arrays nested in objects correctly', () => {
130
+ const result = metrics.prepareMetricFields({
131
+ something: 10,
132
+ anObject: {
133
+ someArray: ['a string', 10, true, false, {id: 'object id'}],
134
+ },
135
+ });
136
+
137
+ assert.deepEqual(result, {
138
+ something: 10,
139
+ anObject_someArray_0: 'a string',
140
+ anObject_someArray_1: 10,
141
+ anObject_someArray_2: true,
142
+ anObject_someArray_3: false,
143
+ anObject_someArray_4_id: 'object id',
144
+ });
145
+ });
146
+
147
+ it('handles arrays nested in arrays correctly', () => {
148
+ const result = metrics.prepareMetricFields({
149
+ something: 10,
150
+ topLevelArray: [
151
+ [1, 2, 'three', {prop1: '1st prop of object 1', prop2: '2nd prop of object 1'}],
152
+ [10, 20, 'thirty', {prop1: '1st prop of object 2', prop2: '2nd prop of object 2'}],
153
+ ],
154
+ });
155
+
156
+ assert.deepEqual(result, {
157
+ something: 10,
158
+ topLevelArray_0_0: 1,
159
+ topLevelArray_0_1: 2,
160
+ topLevelArray_0_2: 'three',
161
+ topLevelArray_0_3_prop1: '1st prop of object 1',
162
+ topLevelArray_0_3_prop2: '2nd prop of object 1',
163
+ topLevelArray_1_0: 10,
164
+ topLevelArray_1_1: 20,
165
+ topLevelArray_1_2: 'thirty',
166
+ topLevelArray_1_3_prop1: '1st prop of object 2',
167
+ topLevelArray_1_3_prop2: '2nd prop of object 2',
168
+ });
169
+ });
170
+
171
+ it('prepends the prefix', () => {
172
+ const result = metrics.prepareMetricFields({
173
+ someStringProp: 'a string',
174
+ numberProp: 111,
175
+ booleanPropFalse: false,
176
+ booleanPropTrue: true,
177
+ }, 'testPrefix');
178
+
179
+ assert.deepEqual(result, {
180
+ testPrefix_someStringProp: 'a string',
181
+ testPrefix_numberProp: 111,
182
+ testPrefix_booleanPropFalse: false,
183
+ testPrefix_booleanPropTrue: true,
184
+ });
185
+ })
186
+ });
61
187
  });
@@ -4,7 +4,7 @@ import sinon from 'sinon';
4
4
  import {assert} from '@webex/test-helper-chai';
5
5
  import {getMaxFs} from '@webex/plugin-meetings/src/multistream/remoteMedia';
6
6
  import FakeTimers from '@sinonjs/fake-timers';
7
- import * as mediaCore from '@webex/internal-media-core';
7
+ import * as InternalMediaCoreModule from '@webex/internal-media-core';
8
8
  import { expect } from 'chai';
9
9
 
10
10
  type ExpectedActiveSpeaker = {
@@ -978,7 +978,7 @@ describe('MediaRequestManager', () => {
978
978
  beforeEach(() => {
979
979
  sendMediaRequestsCallback.resetHistory();
980
980
  getRecommendedMaxBitrateForFrameSizeSpy = sinon.spy(
981
- mediaCore,
981
+ InternalMediaCoreModule,
982
982
  'getRecommendedMaxBitrateForFrameSize'
983
983
  );
984
984
  });
@@ -5,7 +5,7 @@
5
5
  import 'jsdom-global/register';
6
6
  import {assert} from '@webex/test-helper-chai';
7
7
  import sinon from 'sinon';
8
- import {_PERSONAL_ROOM_} from '@webex/plugin-meetings/src/constants';
8
+ import {DESTINATION_TYPE} from '@webex/plugin-meetings/src/constants';
9
9
  import PersonalMeetingRoom from '@webex/plugin-meetings/src/personal-meeting-room';
10
10
 
11
11
  describe('personal-meeting-room', () => {
@@ -23,7 +23,7 @@ describe('personal-meeting-room', () => {
23
23
  it('returns personal meeting room info', async () => {
24
24
  await pmr.get();
25
25
  assert.calledOnce(meetingInfo.fetchMeetingInfo);
26
- assert.calledWith(meetingInfo.fetchMeetingInfo, {type: _PERSONAL_ROOM_});
26
+ assert.calledWith(meetingInfo.fetchMeetingInfo, {type: DESTINATION_TYPE.PERSONAL_ROOM});
27
27
  });
28
28
  });
29
29
  });
@@ -4,15 +4,28 @@ import sinon from 'sinon';
4
4
  import testUtils from '../../../utils/testUtils';
5
5
 
6
6
  // packages/@webex/plugin-meetings/test/unit/spec/reachability/clusterReachability.ts
7
- import {ClusterReachability} from '@webex/plugin-meetings/src/reachability/clusterReachability'; // replace with actual path
7
+ import {
8
+ ClusterReachability,
9
+ ResultEventData,
10
+ Events,
11
+ ClientMediaIpsUpdatedEventData,
12
+ } from '@webex/plugin-meetings/src/reachability/clusterReachability'; // replace with actual path
8
13
 
9
14
  describe('ClusterReachability', () => {
10
15
  let previousRTCPeerConnection;
11
16
  let clusterReachability;
12
17
  let fakePeerConnection;
13
18
 
19
+ const emittedEvents: Record<Events, (ResultEventData | ClientMediaIpsUpdatedEventData)[]> = {
20
+ [Events.resultReady]: [],
21
+ [Events.clientMediaIpsUpdated]: [],
22
+ };
14
23
  const FAKE_OFFER = {type: 'offer', sdp: 'fake sdp'};
15
24
 
25
+ const resetEmittedEvents = () => {
26
+ emittedEvents[Events.resultReady].length = 0;
27
+ emittedEvents[Events.clientMediaIpsUpdated].length = 0;
28
+ };
16
29
  beforeEach(() => {
17
30
  fakePeerConnection = {
18
31
  createOffer: sinon.stub().resolves(FAKE_OFFER),
@@ -30,6 +43,16 @@ describe('ClusterReachability', () => {
30
43
  tcp: ['stun:tcp1.webex.com', 'stun:tcp2.webex.com:5004'],
31
44
  xtls: ['stun:xtls1.webex.com', 'stun:xtls2.webex.com:443'],
32
45
  });
46
+
47
+ resetEmittedEvents();
48
+
49
+ clusterReachability.on(Events.resultReady, (data: ResultEventData) => {
50
+ emittedEvents[Events.resultReady].push(data);
51
+ });
52
+
53
+ clusterReachability.on(Events.clientMediaIpsUpdated, (data: ClientMediaIpsUpdatedEventData) => {
54
+ emittedEvents[Events.clientMediaIpsUpdated].push(data);
55
+ });
33
56
  });
34
57
 
35
58
  afterEach(() => {
@@ -98,6 +121,10 @@ describe('ClusterReachability', () => {
98
121
  tcp: {result: 'untested'},
99
122
  xtls: {result: 'untested'},
100
123
  });
124
+
125
+ // verify that no events were emitted
126
+ assert.deepEqual(emittedEvents[Events.resultReady], []);
127
+ assert.deepEqual(emittedEvents[Events.clientMediaIpsUpdated], []);
101
128
  });
102
129
 
103
130
  describe('#start', () => {
@@ -124,8 +151,12 @@ describe('ClusterReachability', () => {
124
151
  assert.calledOnceWithExactly(fakePeerConnection.createOffer, {offerToReceiveAudio: true});
125
152
  assert.calledOnce(fakePeerConnection.setLocalDescription);
126
153
 
127
- await clock.tickAsync(3000); // move the clock so that reachability times out
154
+ clusterReachability.abort();
128
155
  await promise;
156
+
157
+ // verify that no events were emitted
158
+ assert.deepEqual(emittedEvents[Events.resultReady], []);
159
+ assert.deepEqual(emittedEvents[Events.clientMediaIpsUpdated], []);
129
160
  });
130
161
 
131
162
  it('resolves and has correct result as soon as it finds that all udp, tcp and tls are reachable', async () => {
@@ -134,14 +165,44 @@ describe('ClusterReachability', () => {
134
165
  await clock.tickAsync(100);
135
166
  fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp'}});
136
167
 
168
+ // check the right events were emitted
169
+ assert.equal(emittedEvents[Events.resultReady].length, 1);
170
+ assert.deepEqual(emittedEvents[Events.resultReady][0], {
171
+ protocol: 'udp',
172
+ result: 'reachable',
173
+ latencyInMilliseconds: 100,
174
+ clientMediaIPs: ['somePublicIp'],
175
+ });
176
+
177
+ // clientMediaIpsUpdated shouldn't be emitted, because the IP is already passed in the resultReady event
178
+ assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
179
+
137
180
  await clock.tickAsync(100);
138
181
  fakePeerConnection.onicecandidate({candidate: {type: 'relay', address: 'someTurnRelayIp'}});
139
182
 
183
+ // check the right event was emitted
184
+ assert.equal(emittedEvents[Events.resultReady].length, 2);
185
+ assert.deepEqual(emittedEvents[Events.resultReady][1], {
186
+ protocol: 'tcp',
187
+ result: 'reachable',
188
+ latencyInMilliseconds: 200,
189
+ });
190
+ assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
191
+
140
192
  await clock.tickAsync(100);
141
193
  fakePeerConnection.onicecandidate({
142
194
  candidate: {type: 'relay', address: 'someTurnRelayIp', port: 443},
143
195
  });
144
196
 
197
+ // check the right event was emitted
198
+ assert.equal(emittedEvents[Events.resultReady].length, 3);
199
+ assert.deepEqual(emittedEvents[Events.resultReady][2], {
200
+ protocol: 'xtls',
201
+ result: 'reachable',
202
+ latencyInMilliseconds: 300,
203
+ });
204
+ assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
205
+
145
206
  await promise;
146
207
 
147
208
  assert.deepEqual(clusterReachability.getResult(), {
@@ -151,13 +212,17 @@ describe('ClusterReachability', () => {
151
212
  });
152
213
  });
153
214
 
154
- it('times out correctly', async () => {
215
+ it('resolves and returns correct results when aborted before it gets any candidates', async () => {
155
216
  const promise = clusterReachability.start();
156
217
 
157
218
  // progress time without any candidates
158
- await clock.tickAsync(3000);
219
+ clusterReachability.abort();
159
220
  await promise;
160
221
 
222
+ // verify that no events were emitted
223
+ assert.deepEqual(emittedEvents[Events.resultReady], []);
224
+ assert.deepEqual(emittedEvents[Events.clientMediaIpsUpdated], []);
225
+
161
226
  assert.deepEqual(clusterReachability.getResult(), {
162
227
  udp: {result: 'unreachable'},
163
228
  tcp: {result: 'unreachable'},
@@ -165,22 +230,26 @@ describe('ClusterReachability', () => {
165
230
  });
166
231
  });
167
232
 
168
- it('times out correctly for video mesh nodes', async () => {
169
- clusterReachability = new ClusterReachability('testName', {
170
- isVideoMesh: true,
171
- udp: ['stun:udp1', 'stun:udp2'],
172
- tcp: ['stun:tcp1.webex.com', 'stun:tcp2.webex.com:5004'],
173
- xtls: ['stun:xtls1.webex.com', 'stun:xtls1.webex.com:443'],
174
- });
175
-
233
+ it('resolves and returns correct results when aborted after getting some candidates', async () => {
176
234
  const promise = clusterReachability.start();
177
235
 
178
- // video mesh nodes have shorter timeout of just 1s
179
- await clock.tickAsync(1000);
236
+ await clock.tickAsync(100);
237
+ fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp'}});
238
+
239
+ // check the right event was emitted
240
+ assert.equal(emittedEvents[Events.resultReady].length, 1);
241
+ assert.deepEqual(emittedEvents[Events.resultReady][0], {
242
+ protocol: 'udp',
243
+ result: 'reachable',
244
+ latencyInMilliseconds: 100,
245
+ clientMediaIPs: ['somePublicIp'],
246
+ });
247
+
248
+ clusterReachability.abort();
180
249
  await promise;
181
250
 
182
251
  assert.deepEqual(clusterReachability.getResult(), {
183
- udp: {result: 'unreachable'},
252
+ udp: {result: 'reachable', latencyInMilliseconds: 100, clientMediaIPs: ['somePublicIp']},
184
253
  tcp: {result: 'unreachable'},
185
254
  xtls: {result: 'unreachable'},
186
255
  });
@@ -233,8 +302,7 @@ describe('ClusterReachability', () => {
233
302
  await clock.tickAsync(10);
234
303
  fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp3'}});
235
304
 
236
- await clock.tickAsync(3000); // move the clock so that reachability times out
237
-
305
+ clusterReachability.abort();
238
306
  await promise;
239
307
 
240
308
  // latency should be from only the first candidates, but the clientMediaIps should be from all UDP candidates (not TCP)
@@ -262,8 +330,7 @@ describe('ClusterReachability', () => {
262
330
  await clock.tickAsync(10);
263
331
  fakePeerConnection.onicecandidate({candidate: {type: 'relay', address: 'someTurnRelayIp3'}});
264
332
 
265
- await clock.tickAsync(3000); // move the clock so that reachability times out
266
-
333
+ clusterReachability.abort();
267
334
  await promise;
268
335
 
269
336
  // latency should be from only the first candidates, but the clientMediaIps should be from only from UDP candidates
@@ -293,8 +360,7 @@ describe('ClusterReachability', () => {
293
360
  candidate: {type: 'relay', address: 'someTurnRelayIp3', port: 443},
294
361
  });
295
362
 
296
- await clock.tickAsync(3000); // move the clock so that reachability times out
297
-
363
+ clusterReachability.abort();
298
364
  await promise;
299
365
 
300
366
  // latency should be from only the first candidates, but the clientMediaIps should be from only from UDP candidates
@@ -305,22 +371,50 @@ describe('ClusterReachability', () => {
305
371
  });
306
372
  });
307
373
 
308
- it('ignores duplicate clientMediaIps', async () => {
374
+ it('handles new found public IPs and ignores duplicate IPs', async () => {
309
375
  const promise = clusterReachability.start();
310
376
 
311
377
  // generate candidates with duplicate addresses
312
378
  await clock.tickAsync(10);
313
379
  fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp1'}});
314
380
 
381
+ // check events emitted: there should be a resultReady and no clientMediaIpsUpdated
382
+ assert.equal(emittedEvents[Events.resultReady].length, 1);
383
+ assert.deepEqual(emittedEvents[Events.resultReady][0], {
384
+ protocol: 'udp',
385
+ result: 'reachable',
386
+ latencyInMilliseconds: 10,
387
+ clientMediaIPs: ['somePublicIp1'],
388
+ });
389
+ assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
390
+ resetEmittedEvents();
391
+
315
392
  await clock.tickAsync(10);
316
393
  fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp1'}});
317
394
 
395
+ // no new event was emitted
396
+ assert.equal(emittedEvents[Events.resultReady].length, 0);
397
+ assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
398
+
318
399
  await clock.tickAsync(10);
319
400
  fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp2'}});
320
401
 
402
+ // check new events: now only clientMediaIpsUpdated event and no resultReady events
403
+ assert.equal(emittedEvents[Events.resultReady].length, 0);
404
+ assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 1);
405
+ assert.deepEqual(emittedEvents[Events.clientMediaIpsUpdated][0], {
406
+ protocol: 'udp',
407
+ clientMediaIPs: ['somePublicIp1', 'somePublicIp2'],
408
+ });
409
+ resetEmittedEvents();
410
+
321
411
  await clock.tickAsync(10);
322
412
  fakePeerConnection.onicecandidate({candidate: {type: 'srflx', address: 'somePublicIp2'}});
323
413
 
414
+ // no new event was emitted
415
+ assert.equal(emittedEvents[Events.resultReady].length, 0);
416
+ assert.equal(emittedEvents[Events.clientMediaIpsUpdated].length, 0);
417
+
324
418
  // send also a relay candidate so that the reachability check finishes
325
419
  fakePeerConnection.onicecandidate({candidate: {type: 'relay', address: 'someTurnRelayIp'}});
326
420
  fakePeerConnection.onicecandidate({