@webex/plugin-meetings 3.3.1-next.13 → 3.3.1-next.15

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.
@@ -87,6 +87,7 @@ describe('plugin-meetings', () => {
87
87
  // @ts-ignore
88
88
  webex = new MockWebex({});
89
89
  webex.internal.llm.on = sinon.stub();
90
+ webex.internal.llm.isConnected = sinon.stub();
90
91
  webex.internal.mercury.on = sinon.stub();
91
92
  breakouts = new Breakouts({}, {parent: webex});
92
93
  breakouts.groupId = 'groupId';
@@ -225,38 +226,6 @@ describe('plugin-meetings', () => {
225
226
  });
226
227
  });
227
228
 
228
- describe('#listenToBroadcastMessages', () => {
229
- it('triggers message event when a message received', () => {
230
- const call = webex.internal.llm.on.getCall(0);
231
- const callback = call.args[1];
232
-
233
- assert.equal(call.args[0], 'event:breakout.message');
234
-
235
- let message;
236
-
237
- breakouts.listenTo(breakouts, BREAKOUTS.EVENTS.MESSAGE, (event) => {
238
- message = event;
239
- });
240
-
241
- breakouts.currentBreakoutSession.sessionId = 'sessionId';
242
-
243
- callback({
244
- data: {
245
- senderUserId: 'senderUserId',
246
- sentTime: 'sentTime',
247
- message: 'message',
248
- },
249
- });
250
-
251
- assert.deepEqual(message, {
252
- senderUserId: 'senderUserId',
253
- sentTime: 'sentTime',
254
- message: 'message',
255
- sessionId: 'sessionId',
256
- });
257
- });
258
- });
259
-
260
229
  describe('#listenToBreakoutRosters', () => {
261
230
  it('triggers member update event when a roster received', () => {
262
231
  const call = webex.internal.mercury.on.getCall(0);
@@ -496,8 +465,58 @@ describe('plugin-meetings', () => {
496
465
  describe('#locusUrlUpdate', () => {
497
466
  it('sets the locus url', () => {
498
467
  breakouts.locusUrlUpdate('newUrl');
468
+ assert.equal(breakouts.locusUrl, 'newUrl');
469
+ });
470
+ });
471
+
472
+ describe('#listenToBroadcastMessages', () => {
473
+ it('do not subscribe message if llm not connected', () => {
474
+ webex.internal.llm.isConnected = sinon.stub().returns(false);
475
+ breakouts.listenTo = sinon.stub();
476
+ breakouts.locusUrlUpdate('newUrl');
477
+ assert.equal(breakouts.locusUrl, 'newUrl');
478
+ assert.notCalled(breakouts.listenTo);
479
+ });
499
480
 
481
+ it('do not subscribe message if already done', () => {
482
+ webex.internal.llm.isConnected = sinon.stub().returns(true);
483
+ breakouts.hasSubscribedToMessage = true;
484
+ breakouts.listenTo = sinon.stub();
485
+ breakouts.locusUrlUpdate('newUrl');
500
486
  assert.equal(breakouts.locusUrl, 'newUrl');
487
+ assert.notCalled(breakouts.listenTo);
488
+ });
489
+
490
+ it('triggers message event when a message received', () => {
491
+ webex.internal.llm.isConnected = sinon.stub().returns(true);
492
+ breakouts.locusUrlUpdate('newUrl');
493
+ const call = webex.internal.llm.on.getCall(0);
494
+ const callback = call.args[1];
495
+
496
+ assert.equal(call.args[0], 'event:breakout.message');
497
+
498
+ let message;
499
+
500
+ breakouts.listenTo(breakouts, BREAKOUTS.EVENTS.MESSAGE, (event) => {
501
+ message = event;
502
+ });
503
+
504
+ breakouts.currentBreakoutSession.sessionId = 'sessionId';
505
+
506
+ callback({
507
+ data: {
508
+ senderUserId: 'senderUserId',
509
+ sentTime: 'sentTime',
510
+ message: 'message',
511
+ },
512
+ });
513
+
514
+ assert.deepEqual(message, {
515
+ senderUserId: 'senderUserId',
516
+ sentTime: 'sentTime',
517
+ message: 'message',
518
+ sessionId: 'sessionId',
519
+ });
501
520
  });
502
521
  });
503
522
 
@@ -6275,7 +6275,7 @@ describe('plugin-meetings', () => {
6275
6275
  },
6276
6276
  'SELF_OBSERVING'
6277
6277
  );
6278
-
6278
+
6279
6279
 
6280
6280
  // Verify that the event handler behaves as expected
6281
6281
  expect(meeting.statsAnalyzer.stopAnalyzer.calledOnce).to.be.true;
@@ -9781,6 +9781,7 @@ describe('plugin-meetings', () => {
9781
9781
  beforeEach(() => {
9782
9782
  webex.internal.llm.isConnected = sinon.stub().returns(false);
9783
9783
  webex.internal.llm.getLocusUrl = sinon.stub();
9784
+ webex.internal.llm.getDatachannelUrl = sinon.stub();
9784
9785
  webex.internal.llm.registerAndConnect = sinon
9785
9786
  .stub()
9786
9787
  .returns(Promise.resolve('something'));
@@ -9808,6 +9809,7 @@ describe('plugin-meetings', () => {
9808
9809
  meeting.joinedWith = {state: 'JOINED'};
9809
9810
  webex.internal.llm.isConnected.returns(true);
9810
9811
  webex.internal.llm.getLocusUrl.returns('a url');
9812
+ webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
9811
9813
 
9812
9814
  meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
9813
9815
 
@@ -9844,6 +9846,7 @@ describe('plugin-meetings', () => {
9844
9846
  meeting.joinedWith = {state: 'JOINED'};
9845
9847
  webex.internal.llm.isConnected.returns(true);
9846
9848
  webex.internal.llm.getLocusUrl.returns('a url');
9849
+ webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
9847
9850
 
9848
9851
  meeting.locusInfo = {url: 'a different url', info: {datachannelUrl: 'a datachannel url'}};
9849
9852
 
@@ -9869,6 +9872,36 @@ describe('plugin-meetings', () => {
9869
9872
  );
9870
9873
  });
9871
9874
 
9875
+ it('disconnects if first if the data channel url has changed', async () => {
9876
+ meeting.joinedWith = {state: 'JOINED'};
9877
+ webex.internal.llm.isConnected.returns(true);
9878
+ webex.internal.llm.getLocusUrl.returns('a url');
9879
+ webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
9880
+
9881
+ meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a different datachannel url'}};
9882
+
9883
+ const result = await meeting.updateLLMConnection();
9884
+
9885
+ assert.calledWith(webex.internal.llm.disconnectLLM);
9886
+ assert.calledWith(
9887
+ webex.internal.llm.registerAndConnect,
9888
+ 'a url',
9889
+ 'a different datachannel url'
9890
+ );
9891
+ assert.equal(result, 'something');
9892
+ assert.calledWithExactly(
9893
+ meeting.webex.internal.llm.off,
9894
+ 'event:relay.event',
9895
+ meeting.processRelayEvent
9896
+ );
9897
+ assert.calledTwice(meeting.webex.internal.llm.off);
9898
+ assert.calledOnceWithExactly(
9899
+ meeting.webex.internal.llm.on,
9900
+ 'event:relay.event',
9901
+ meeting.processRelayEvent
9902
+ );
9903
+ });
9904
+
9872
9905
  it('disconnects when the state is not JOINED', async () => {
9873
9906
  meeting.joinedWith = {state: 'any other state'};
9874
9907
  webex.internal.llm.isConnected.returns(true);
@@ -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({