@webex/plugin-meetings 3.4.0 → 3.5.0-next.2

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 (63) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/interpretation/index.js +1 -1
  4. package/dist/interpretation/siLanguage.js +1 -1
  5. package/dist/media/index.js +6 -9
  6. package/dist/media/index.js.map +1 -1
  7. package/dist/meeting/index.js +151 -50
  8. package/dist/meeting/index.js.map +1 -1
  9. package/dist/meeting/util.js +1 -0
  10. package/dist/meeting/util.js.map +1 -1
  11. package/dist/meetings/index.js +11 -2
  12. package/dist/meetings/index.js.map +1 -1
  13. package/dist/reachability/index.js +175 -103
  14. package/dist/reachability/index.js.map +1 -1
  15. package/dist/reconnection-manager/index.js +1 -1
  16. package/dist/reconnection-manager/index.js.map +1 -1
  17. package/dist/rtcMetrics/index.js +26 -6
  18. package/dist/rtcMetrics/index.js.map +1 -1
  19. package/dist/types/meeting/index.d.ts +22 -2
  20. package/dist/types/meetings/index.d.ts +4 -2
  21. package/dist/types/reachability/index.d.ts +14 -2
  22. package/dist/types/rtcMetrics/index.d.ts +11 -1
  23. package/dist/webinar/index.js +1 -1
  24. package/package.json +22 -22
  25. package/src/media/index.ts +5 -9
  26. package/src/meeting/index.ts +88 -10
  27. package/src/meeting/util.ts +2 -0
  28. package/src/meetings/index.ts +11 -4
  29. package/src/reachability/index.ts +49 -4
  30. package/src/reconnection-manager/index.ts +1 -1
  31. package/src/rtcMetrics/index.ts +25 -5
  32. package/test/integration/spec/converged-space-meetings.js +1 -1
  33. package/test/unit/spec/breakouts/index.ts +1 -0
  34. package/test/unit/spec/interceptors/locusRetry.ts +11 -10
  35. package/test/unit/spec/media/MediaConnectionAwaiter.ts +1 -0
  36. package/test/unit/spec/media/index.ts +34 -7
  37. package/test/unit/spec/media/properties.ts +1 -1
  38. package/test/unit/spec/meeting/connectionStateHandler.ts +1 -0
  39. package/test/unit/spec/meeting/index.js +116 -12
  40. package/test/unit/spec/meeting/locusMediaRequest.ts +3 -2
  41. package/test/unit/spec/meeting/request.js +1 -0
  42. package/test/unit/spec/meeting/utils.js +4 -0
  43. package/test/unit/spec/meeting-info/meetinginfov2.js +10 -11
  44. package/test/unit/spec/meeting-info/request.js +1 -1
  45. package/test/unit/spec/meetings/index.js +40 -5
  46. package/test/unit/spec/members/request.js +2 -1
  47. package/test/unit/spec/multistream/mediaRequestManager.ts +1 -0
  48. package/test/unit/spec/multistream/receiveSlot.ts +1 -0
  49. package/test/unit/spec/multistream/receiveSlotManager.ts +1 -0
  50. package/test/unit/spec/multistream/remoteMedia.ts +1 -0
  51. package/test/unit/spec/multistream/remoteMediaGroup.ts +1 -0
  52. package/test/unit/spec/multistream/remoteMediaManager.ts +1 -0
  53. package/test/unit/spec/multistream/sendSlotManager.ts +1 -0
  54. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +0 -1
  55. package/test/unit/spec/reachability/index.ts +211 -13
  56. package/test/unit/spec/reachability/request.js +1 -0
  57. package/test/unit/spec/roap/request.ts +1 -0
  58. package/test/unit/spec/rtcMetrics/index.ts +31 -0
  59. package/dist/networkQualityMonitor/index.js +0 -227
  60. package/dist/networkQualityMonitor/index.js.map +0 -1
  61. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  62. package/src/networkQualityMonitor/index.ts +0 -211
  63. package/test/unit/spec/networkQualityMonitor/index.js +0 -99
@@ -93,6 +93,8 @@ export default class Reachability extends EventsScope {
93
93
  expectedResultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
94
94
  resultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
95
95
 
96
+ protected lastTrigger?: string;
97
+
96
98
  /**
97
99
  * Creates an instance of Reachability.
98
100
  * @param {object} webex
@@ -114,18 +116,50 @@ export default class Reachability extends EventsScope {
114
116
  this.clusterReachability = {};
115
117
  }
116
118
 
119
+ /**
120
+ * Fetches the list of media clusters from the backend
121
+ * @param {boolean} isRetry
122
+ * @private
123
+ * @returns {Promise<{clusters: ClusterList, joinCookie: any}>}
124
+ */
125
+ async getClusters(isRetry = false): Promise<{clusters: ClusterList; joinCookie: any}> {
126
+ try {
127
+ const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
128
+ MeetingUtil.getIpVersion(this.webex)
129
+ );
130
+
131
+ return {clusters, joinCookie};
132
+ } catch (error) {
133
+ if (isRetry) {
134
+ throw error;
135
+ }
136
+
137
+ LoggerProxy.logger.error(
138
+ `Reachability:index#getClusters --> Failed with error: ${error}, retrying...`
139
+ );
140
+
141
+ return this.getClusters(true);
142
+ }
143
+ }
144
+
117
145
  /**
118
146
  * Gets a list of media clusters from the backend and performs reachability checks on all the clusters
147
+ * @param {string} trigger - explains the reason for starting reachability
119
148
  * @returns {Promise<ReachabilityResults>} reachability results
120
149
  * @public
121
150
  * @memberof Reachability
122
151
  */
123
- public async gatherReachability(): Promise<ReachabilityResults> {
152
+ public async gatherReachability(trigger: string): Promise<ReachabilityResults> {
124
153
  // Fetch clusters and measure latency
125
154
  try {
126
- const {clusters, joinCookie} = await this.reachabilityRequest.getClusters(
127
- MeetingUtil.getIpVersion(this.webex)
128
- );
155
+ this.lastTrigger = trigger;
156
+
157
+ // kick off ip version detection. For now we don't await it, as we're doing it
158
+ // to gather the timings and send them with our reachability metrics
159
+ // @ts-ignore
160
+ this.webex.internal.device.ipNetworkDetector.detect();
161
+
162
+ const {clusters, joinCookie} = await this.getClusters();
129
163
 
130
164
  // @ts-ignore
131
165
  await this.webex.boundedStorage.put(
@@ -513,6 +547,17 @@ export default class Reachability extends EventsScope {
513
547
  tcp: this.getStatistics(results, 'tcp', false),
514
548
  xtls: this.getStatistics(results, 'xtls', false),
515
549
  },
550
+ ipver: {
551
+ // @ts-ignore
552
+ firstIpV4: this.webex.internal.device.ipNetworkDetector.firstIpV4,
553
+ // @ts-ignore
554
+ firstIpV6: this.webex.internal.device.ipNetworkDetector.firstIpV6,
555
+ // @ts-ignore
556
+ firstMdns: this.webex.internal.device.ipNetworkDetector.firstMdns,
557
+ // @ts-ignore
558
+ totalTime: this.webex.internal.device.ipNetworkDetector.totalTime,
559
+ },
560
+ trigger: this.lastTrigger,
516
561
  };
517
562
  Metrics.sendBehavioralMetric(
518
563
  BEHAVIORAL_METRICS.REACHABILITY_COMPLETED,
@@ -342,7 +342,7 @@ export default class ReconnectionManager {
342
342
  }
343
343
 
344
344
  try {
345
- await this.webex.meetings.startReachability();
345
+ await this.webex.meetings.startReachability('reconnection');
346
346
  } catch (err) {
347
347
  LoggerProxy.logger.info(
348
348
  'ReconnectionManager:index#reconnect --> Reachability failed, continuing with reconnection attempt, err: ',
@@ -34,6 +34,8 @@ export default class RtcMetrics {
34
34
 
35
35
  connectionId: string;
36
36
 
37
+ shouldSendMetricsOnNextStatsReport: boolean;
38
+
37
39
  /**
38
40
  * Initialize the interval.
39
41
  *
@@ -47,9 +49,7 @@ export default class RtcMetrics {
47
49
  this.meetingId = meetingId;
48
50
  this.webex = webex;
49
51
  this.correlationId = correlationId;
50
- this.setNewConnectionId();
51
- // Send the first set of metrics at 5 seconds in the case of a user leaving the call shortly after joining.
52
- setTimeout(this.sendMetricsInQueue.bind(this), 5 * 1000);
52
+ this.resetConnection();
53
53
  }
54
54
 
55
55
  /**
@@ -64,6 +64,18 @@ export default class RtcMetrics {
64
64
  }
65
65
  }
66
66
 
67
+ /**
68
+ * Forces sending metrics when we get the next stats-report
69
+ *
70
+ * This is useful for cases when something important happens that affects the media connection,
71
+ * for example when we move from lobby into the meeting.
72
+ *
73
+ * @returns {void}
74
+ */
75
+ public sendNextMetrics() {
76
+ this.shouldSendMetricsOnNextStatsReport = true;
77
+ }
78
+
67
79
  /**
68
80
  * Add metrics items to the metrics queue.
69
81
  *
@@ -79,6 +91,13 @@ export default class RtcMetrics {
79
91
 
80
92
  this.metricsQueue.push(data);
81
93
 
94
+ if (this.shouldSendMetricsOnNextStatsReport && data.name === 'stats-report') {
95
+ // this is the first useful set of data (WCME gives it to us after 5s), send it out immediately
96
+ // in case the user is unhappy and closes the browser early
97
+ this.sendMetricsInQueue();
98
+ this.shouldSendMetricsOnNextStatsReport = false;
99
+ }
100
+
82
101
  try {
83
102
  // If a connection fails, send the rest of the metrics in queue and get a new connection id.
84
103
  const parsedPayload = parseJsonPayload(data.payload);
@@ -88,7 +107,7 @@ export default class RtcMetrics {
88
107
  parsedPayload.value === 'failed'
89
108
  ) {
90
109
  this.sendMetricsInQueue();
91
- this.setNewConnectionId();
110
+ this.resetConnection();
92
111
  }
93
112
  } catch (e) {
94
113
  console.error(e);
@@ -130,8 +149,9 @@ export default class RtcMetrics {
130
149
  *
131
150
  * @returns {void}
132
151
  */
133
- private setNewConnectionId() {
152
+ private resetConnection() {
134
153
  this.connectionId = uuid.v4();
154
+ this.shouldSendMetricsOnNextStatsReport = true;
135
155
  }
136
156
 
137
157
  /**
@@ -1,5 +1,5 @@
1
- import { config } from 'dotenv';
2
1
  import 'jsdom-global/register';
2
+ import {config} from 'dotenv';
3
3
  import {assert} from '@webex/test-helper-chai';
4
4
  import {skipInNode} from '@webex/test-helper-mocha';
5
5
  import BrowserDetection from '@webex/plugin-meetings/dist/common/browser-detection';
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import {assert, expect} from '@webex/test-helper-chai';
2
3
  import Breakouts from '@webex/plugin-meetings/src/breakouts';
3
4
  import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  /* eslint-disable camelcase */
6
+ import 'jsdom-global/register';
6
7
  import {assert} from '@webex/test-helper-chai';
7
8
  import { expect } from "@webex/test-helper-chai";
8
9
  import MockWebex from '@webex/test-helper-mock-webex';
@@ -13,7 +14,7 @@ import sinon from 'sinon';
13
14
 
14
15
  describe('plugin-meetings', () => {
15
16
  describe('Interceptors', () => {
16
- describe('LocusRetryStatusInterceptor', () => {
17
+ describe('LocusRetryStatusInterceptor', () => {
17
18
  let interceptor, webex;
18
19
  beforeEach(() => {
19
20
  webex = new MockWebex({
@@ -24,7 +25,7 @@ describe('plugin-meetings', () => {
24
25
  interceptor = Reflect.apply(LocusRetryStatusInterceptor.create, {
25
26
  sessionId: 'mock-webex_uuid',
26
27
  }, []);
27
- });
28
+ });
28
29
  describe('#onResponseError', () => {
29
30
  const options = {
30
31
  method: 'POST',
@@ -41,7 +42,7 @@ describe('plugin-meetings', () => {
41
42
  headers: {
42
43
  trackingid: 'test',
43
44
  'retry-after': 1000,
44
- },
45
+ },
45
46
  uri: `https://locus-test.webex.com/locus/api/v1/loci/call`,
46
47
  },
47
48
  body: {
@@ -54,7 +55,7 @@ describe('plugin-meetings', () => {
54
55
  headers: {
55
56
  trackingid: 'test',
56
57
  'retry-after': 1000,
57
- },
58
+ },
58
59
  uri: `https://locus-test.webex.com/locus/api/v1/loci/call`,
59
60
  },
60
61
  body: {
@@ -73,7 +74,7 @@ describe('plugin-meetings', () => {
73
74
 
74
75
  return interceptor.onResponseError(options, reason2).then(() => {
75
76
  expect(handleRetryStub.calledWith(options, 1000)).to.be.true;
76
-
77
+
77
78
  });
78
79
  });
79
80
  });
@@ -92,7 +93,7 @@ describe('plugin-meetings', () => {
92
93
  it('returns the correct resolved value when the request is successful', () => {
93
94
  const mockResponse = 'mock response'
94
95
  interceptor.webex.request = sinon.stub().returns(Promise.resolve(mockResponse));
95
-
96
+
96
97
  return interceptor.handleRetryRequestLocusServiceError(options, retryAfterTime)
97
98
  .then((response) => {
98
99
  expect(response).to.equal(mockResponse);
@@ -101,9 +102,9 @@ describe('plugin-meetings', () => {
101
102
 
102
103
  it('rejects the promise when the request is unsuccessful', () => {
103
104
  const rejectionReason = 'Service Unavaialble after retry';
104
-
105
+
105
106
  interceptor.webex.request = sinon.stub().returns(Promise.reject(rejectionReason));
106
-
107
+
107
108
  return interceptor.handleRetryRequestLocusServiceError(options, retryAfterTime)
108
109
  .catch((error) => {
109
110
  expect(error).to.equal(rejectionReason);
@@ -114,10 +115,10 @@ describe('plugin-meetings', () => {
114
115
  let clock;
115
116
  clock = sinon.useFakeTimers();
116
117
  const mockResponse = 'mock response'
117
-
118
+
118
119
  interceptor.webex.request = sinon.stub().returns(Promise.resolve(mockResponse));
119
120
  const promise = interceptor.handleRetryRequestLocusServiceError(options, retryAfterTime);
120
-
121
+
121
122
  clock.tick(retryAfterTime);
122
123
 
123
124
  return promise.then(() => {
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register';
1
2
  import {assert} from '@webex/test-helper-chai';
2
3
  import sinon from 'sinon';
3
4
  import {ConnectionState, MediaConnectionEventNames} from '@webex/internal-media-core';
@@ -1,16 +1,15 @@
1
+ import 'jsdom-global/register';
1
2
  import * as InternalMediaCoreModule from '@webex/internal-media-core';
2
3
  import Media from '@webex/plugin-meetings/src/media/index';
3
4
  import {assert} from '@webex/test-helper-chai';
4
5
  import sinon from 'sinon';
5
6
  import StaticConfig from '@webex/plugin-meetings/src/common/config';
6
- import MockWebex from '@webex/test-helper-mock-webex';
7
7
 
8
8
  describe('createMediaConnection', () => {
9
9
  let clock;
10
10
  beforeEach(() => {
11
11
  clock = sinon.useFakeTimers();
12
12
  });
13
- const webex = MockWebex();
14
13
 
15
14
  const fakeRoapMediaConnection = {
16
15
  id: 'roap media connection',
@@ -61,7 +60,7 @@ describe('createMediaConnection', () => {
61
60
  const ENABLE_EXTMAP = false;
62
61
  const ENABLE_RTX = true;
63
62
 
64
- Media.createMediaConnection(false, 'some debug id', webex, 'meetingId', 'correlationId', {
63
+ Media.createMediaConnection(false, 'some debug id', 'meetingId', {
65
64
  mediaProperties: {
66
65
  mediaDirection: {
67
66
  sendAudio: false,
@@ -139,7 +138,13 @@ describe('createMediaConnection', () => {
139
138
  .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
140
139
  .returns(fakeRoapMediaConnection);
141
140
 
142
- Media.createMediaConnection(true, 'some debug id', webex, 'meeting id', 'correlationId', {
141
+ const rtcMetrics = {
142
+ addMetrics: sinon.stub(),
143
+ closeMetrics: sinon.stub(),
144
+ sendMetricsInQueue: sinon.stub(),
145
+ };
146
+
147
+ Media.createMediaConnection(true, 'some debug id', 'meeting id', {
143
148
  mediaProperties: {
144
149
  mediaDirection: {
145
150
  sendAudio: true,
@@ -150,6 +155,7 @@ describe('createMediaConnection', () => {
150
155
  receiveShare: true,
151
156
  },
152
157
  },
158
+ rtcMetrics,
153
159
  turnServerInfo: {
154
160
  url: 'turns:turn-server-url:443?transport=tcp',
155
161
  username: 'turn username',
@@ -177,6 +183,27 @@ describe('createMediaConnection', () => {
177
183
  },
178
184
  'meeting id'
179
185
  );
186
+
187
+ // check if rtcMetrics callbacks are configured correctly
188
+ const addMetricsCallback = multistreamRoapMediaConnectionConstructorStub.getCalls()[0].args[2];
189
+ const closeMetricsCallback = multistreamRoapMediaConnectionConstructorStub.getCalls()[0].args[3];
190
+ const sendMetricsInQueueCallback = multistreamRoapMediaConnectionConstructorStub.getCalls()[0].args[4];
191
+
192
+ assert.isFunction(addMetricsCallback);
193
+ assert.isFunction(closeMetricsCallback);
194
+ assert.isFunction(sendMetricsInQueueCallback);
195
+
196
+ const fakeMetricsData = {id: 'metrics data'};
197
+
198
+ addMetricsCallback(fakeMetricsData);
199
+ assert.calledOnceWithExactly(rtcMetrics.addMetrics, fakeMetricsData);
200
+
201
+ closeMetricsCallback();
202
+ assert.calledOnce(rtcMetrics.closeMetrics);
203
+
204
+ sendMetricsInQueueCallback();
205
+ assert.calledOnce(rtcMetrics.sendMetricsInQueue);
206
+
180
207
  });
181
208
 
182
209
  [
@@ -191,7 +218,7 @@ describe('createMediaConnection', () => {
191
218
  .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
192
219
  .returns(fakeRoapMediaConnection);
193
220
 
194
- Media.createMediaConnection(true, 'debug string', webex, 'meeting id', 'correlationId', {
221
+ Media.createMediaConnection(true, 'debug string', 'meeting id', {
195
222
  mediaProperties: {
196
223
  mediaDirection: {
197
224
  sendAudio: true,
@@ -220,7 +247,7 @@ describe('createMediaConnection', () => {
220
247
  .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
221
248
  .returns(fakeRoapMediaConnection);
222
249
 
223
- Media.createMediaConnection(true, 'debug string', webex, 'meeting id', 'correlationId', {
250
+ Media.createMediaConnection(true, 'debug string', 'meeting id', {
224
251
  mediaProperties: {
225
252
  mediaDirection: {
226
253
  sendAudio: true,
@@ -260,7 +287,7 @@ describe('createMediaConnection', () => {
260
287
  const ENABLE_EXTMAP = false;
261
288
  const ENABLE_RTX = true;
262
289
 
263
- Media.createMediaConnection(false, 'some debug id', webex, 'meeting id', 'correlationId', {
290
+ Media.createMediaConnection(false, 'some debug id', 'meeting id', {
264
291
  mediaProperties: {
265
292
  mediaDirection: {
266
293
  sendAudio: true,
@@ -1,8 +1,8 @@
1
+ import 'jsdom-global/register';
1
2
  import {assert} from '@webex/test-helper-chai';
2
3
  import sinon from 'sinon';
3
4
  import {ConnectionState} from '@webex/internal-media-core';
4
5
  import MediaProperties from '@webex/plugin-meetings/src/media/properties';
5
- import testUtils from '../../../utils/testUtils';
6
6
  import {Defer} from '@webex/common';
7
7
  import MediaConnectionAwaiter from '../../../../src/media/MediaConnectionAwaiter';
8
8
 
@@ -1,3 +1,4 @@
1
+ import 'jsdom-global/register'
1
2
  import sinon from 'sinon';
2
3
  import {assert} from '@webex/test-helper-chai';
3
4
  import {
@@ -5,6 +5,8 @@ import 'jsdom-global/register';
5
5
  import {cloneDeep, forEach, isEqual, isUndefined} from 'lodash';
6
6
  import sinon from 'sinon';
7
7
  import * as InternalMediaCoreModule from '@webex/internal-media-core';
8
+ import * as RtcMetricsModule from '@webex/plugin-meetings/src/rtcMetrics';
9
+ import * as RemoteMediaManagerModule from '@webex/plugin-meetings/src/multistream/remoteMediaManager';
8
10
  import StateMachine from 'javascript-state-machine';
9
11
  import uuid from 'uuid';
10
12
  import {assert, expect} from '@webex/test-helper-chai';
@@ -305,7 +307,7 @@ describe('plugin-meetings', () => {
305
307
  assert.equal(meeting.resource, uuid2);
306
308
  assert.equal(meeting.deviceUrl, uuid3);
307
309
  assert.equal(meeting.correlationId, correlationId);
308
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
310
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
309
311
  assert.deepEqual(meeting.meetingInfo, {});
310
312
  assert.instanceOf(meeting.members, Members);
311
313
  assert.calledOnceWithExactly(
@@ -329,6 +331,7 @@ describe('plugin-meetings', () => {
329
331
  assert.isNull(meeting.partner);
330
332
  assert.isNull(meeting.type);
331
333
  assert.isNull(meeting.owner);
334
+ assert.isUndefined(meeting.isoLocalClientMeetingJoinTime);
332
335
  assert.isNull(meeting.hostId);
333
336
  assert.isNull(meeting.policy);
334
337
  assert.instanceOf(meeting.meetingRequest, MeetingRequest);
@@ -373,7 +376,7 @@ describe('plugin-meetings', () => {
373
376
  }
374
377
  );
375
378
  assert.equal(newMeeting.correlationId, newMeeting.id);
376
- assert.deepEqual(newMeeting.callStateForMetrics, {correlationId: newMeeting.id});
379
+ assert.deepEqual(newMeeting.callStateForMetrics, {correlationId: newMeeting.id, sessionCorrelationId: ''});
377
380
  });
378
381
 
379
382
  it('correlationId can be provided in callStateForMetrics', () => {
@@ -400,6 +403,36 @@ describe('plugin-meetings', () => {
400
403
  correlationId: uuid4,
401
404
  joinTrigger: 'fake-join-trigger',
402
405
  loginType: 'fake-login-type',
406
+ sessionCorrelationId: '',
407
+ });
408
+ });
409
+
410
+ it('sessionCorrelationId can be provided in callStateForMetrics', () => {
411
+ const newMeeting = new Meeting(
412
+ {
413
+ userId: uuid1,
414
+ resource: uuid2,
415
+ deviceUrl: uuid3,
416
+ locus: {url: url1},
417
+ destination: testDestination,
418
+ destinationType: DESTINATION_TYPE.MEETING_ID,
419
+ callStateForMetrics: {
420
+ correlationId: uuid4,
421
+ sessionCorrelationId: uuid1,
422
+ joinTrigger: 'fake-join-trigger',
423
+ loginType: 'fake-login-type',
424
+ },
425
+ },
426
+ {
427
+ parent: webex,
428
+ }
429
+ );
430
+ assert.exists(newMeeting.sessionCorrelationId);
431
+ assert.deepEqual(newMeeting.callStateForMetrics, {
432
+ correlationId: uuid4,
433
+ sessionCorrelationId: uuid1,
434
+ joinTrigger: 'fake-join-trigger',
435
+ loginType: 'fake-login-type',
403
436
  });
404
437
  });
405
438
 
@@ -1585,6 +1618,10 @@ describe('plugin-meetings', () => {
1585
1618
  sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
1586
1619
  });
1587
1620
 
1621
+ afterEach(() => {
1622
+ assert.exists(meeting.isoLocalClientMeetingJoinTime);
1623
+ });
1624
+
1588
1625
  it('should join the meeting and return promise', async () => {
1589
1626
  const join = meeting.join({pstnAudioType: 'dial-in'});
1590
1627
  meeting.config.enableAutomaticLLM = true;
@@ -1603,6 +1640,7 @@ describe('plugin-meetings', () => {
1603
1640
  const result = await join;
1604
1641
 
1605
1642
  assert.calledOnce(MeetingUtil.joinMeeting);
1643
+ assert.calledOnce(webex.internal.device.meetingStarted);
1606
1644
  assert.calledOnce(meeting.setLocus);
1607
1645
  assert.equal(result, joinMeetingResult);
1608
1646
  assert.calledWith(webex.internal.llm.on, 'online', meeting.handleLLMOnline);
@@ -2405,9 +2443,7 @@ describe('plugin-meetings', () => {
2405
2443
  Media.createMediaConnection,
2406
2444
  false,
2407
2445
  meeting.getMediaConnectionDebugId(),
2408
- webex,
2409
2446
  meeting.id,
2410
- meeting.correlationId,
2411
2447
  sinon.match({turnServerInfo: undefined})
2412
2448
  );
2413
2449
  assert.calledOnce(meeting.setMercuryListener);
@@ -2449,6 +2485,44 @@ describe('plugin-meetings', () => {
2449
2485
  checkWorking({allowMediaInLobby: true});
2450
2486
  });
2451
2487
 
2488
+ it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
2489
+ const fakeRtcMetrics = {id: 'fake rtc metrics object'};
2490
+ const rtcMetricsCtor = sinon.stub(RtcMetricsModule, 'default').returns(fakeRtcMetrics);
2491
+
2492
+ // setup the minimum mocks required for multistream connection
2493
+ fakeMediaConnection.createSendSlot = sinon.stub().returns({
2494
+ publishStream: sinon.stub(),
2495
+ unpublishStream: sinon.stub(),
2496
+ setNamedMediaGroups: sinon.stub(),
2497
+ });
2498
+ sinon.stub(RemoteMediaManagerModule, 'RemoteMediaManager').returns({
2499
+ start: sinon.stub().resolves(),
2500
+ on: sinon.stub(),
2501
+ logAllReceiveSlots: sinon.stub(),
2502
+ });
2503
+
2504
+ meeting.meetingState = 'ACTIVE';
2505
+ meeting.isMultistream = true;
2506
+
2507
+ await meeting.addMedia({
2508
+ mediaSettings: {},
2509
+ });
2510
+
2511
+ assert.calledOnceWithExactly(rtcMetricsCtor, webex, meeting.id, meeting.correlationId);
2512
+
2513
+ // check that rtcMetrics was passed to Media.createMediaConnection
2514
+ assert.calledOnce(Media.createMediaConnection);
2515
+ assert.calledWith(
2516
+ Media.createMediaConnection,
2517
+ true,
2518
+ meeting.getMediaConnectionDebugId(),
2519
+ meeting.id,
2520
+ sinon.match({
2521
+ rtcMetrics: fakeRtcMetrics,
2522
+ })
2523
+ );
2524
+ });
2525
+
2452
2526
  it('should pass the turn server info to the peer connection', async () => {
2453
2527
  const FAKE_TURN_URL = 'turns:webex.com:3478';
2454
2528
  const FAKE_TURN_USER = 'some-turn-username';
@@ -2478,9 +2552,7 @@ describe('plugin-meetings', () => {
2478
2552
  Media.createMediaConnection,
2479
2553
  false,
2480
2554
  meeting.getMediaConnectionDebugId(),
2481
- webex,
2482
2555
  meeting.id,
2483
- meeting.correlationId,
2484
2556
  sinon.match({
2485
2557
  turnServerInfo: {
2486
2558
  url: FAKE_TURN_URL,
@@ -3023,6 +3095,8 @@ describe('plugin-meetings', () => {
3023
3095
  }),
3024
3096
  };
3025
3097
  meeting.iceCandidatesCount = 3;
3098
+ meeting.iceCandidateErrors.set('701_error', 3);
3099
+ meeting.iceCandidateErrors.set('701_turn_host_lookup_received_error', 1);
3026
3100
 
3027
3101
  await meeting.addMedia({
3028
3102
  mediaSettings: {},
@@ -3044,6 +3118,8 @@ describe('plugin-meetings', () => {
3044
3118
  someReachabilityMetric1: 'some value1',
3045
3119
  someReachabilityMetric2: 'some value2',
3046
3120
  iceCandidatesCount: 3,
3121
+ '701_error': 3,
3122
+ '701_turn_host_lookup_received_error': 1,
3047
3123
  }
3048
3124
  );
3049
3125
 
@@ -3397,9 +3473,7 @@ describe('plugin-meetings', () => {
3397
3473
  Media.createMediaConnection,
3398
3474
  false,
3399
3475
  meeting.getMediaConnectionDebugId(),
3400
- webex,
3401
3476
  meeting.id,
3402
- meeting.correlationId,
3403
3477
  sinon.match({
3404
3478
  turnServerInfo: {
3405
3479
  url: FAKE_TURN_URL,
@@ -4240,6 +4314,20 @@ describe('plugin-meetings', () => {
4240
4314
  assert.calledTwice(locusMediaRequestStub);
4241
4315
  });
4242
4316
 
4317
+ it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
4318
+ const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4319
+ await meeting.addMedia({audioEnabled: false});
4320
+ //calling handleDeviceLogging with audioEnaled as true adn videoEnabled as false
4321
+ assert.calledWith(handleDeviceLoggingSpy,false,true);
4322
+ });
4323
+
4324
+ it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
4325
+ const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4326
+ await meeting.addMedia({videoEnabled: false});
4327
+ //calling handleDeviceLogging audioEnabled as true videoEnabled as false
4328
+ assert.calledWith(handleDeviceLoggingSpy,true,false);
4329
+ });
4330
+
4243
4331
  it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
4244
4332
  await meeting.addMedia({videoEnabled: false});
4245
4333
  await simulateRoapOffer();
@@ -4306,6 +4394,14 @@ describe('plugin-meetings', () => {
4306
4394
  assert.calledTwice(locusMediaRequestStub);
4307
4395
  });
4308
4396
 
4397
+
4398
+ it('addMedia() works correctly when both shareAudio and shareVideo is disabled with no streams publish', async () => {
4399
+ const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4400
+ await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false});
4401
+ //calling handleDeviceLogging with audioEnabled true and videoEnabled as true
4402
+ assert.calledWith(handleDeviceLoggingSpy,true,true);
4403
+ });
4404
+
4309
4405
  describe('publishStreams()/unpublishStreams() calls', () => {
4310
4406
  [
4311
4407
  {mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
@@ -6861,33 +6957,36 @@ describe('plugin-meetings', () => {
6861
6957
  describe('#setCorrelationId', () => {
6862
6958
  it('should set the correlationId and return undefined', () => {
6863
6959
  assert.equal(meeting.correlationId, correlationId);
6864
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
6960
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6865
6961
  meeting.setCorrelationId(uuid1);
6866
6962
  assert.equal(meeting.correlationId, uuid1);
6867
- assert.deepEqual(meeting.callStateForMetrics, {correlationId: uuid1});
6963
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId: uuid1, sessionCorrelationId: ''});
6868
6964
  });
6869
6965
  });
6870
6966
 
6871
6967
  describe('#updateCallStateForMetrics', () => {
6872
6968
  it('should update the callState, overriding existing values', () => {
6873
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
6969
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6874
6970
  meeting.updateCallStateForMetrics({
6875
6971
  correlationId: uuid1,
6972
+ sessionCorrelationId: uuid3,
6876
6973
  joinTrigger: 'jt',
6877
6974
  loginType: 'lt',
6878
6975
  });
6879
6976
  assert.deepEqual(meeting.callStateForMetrics, {
6880
6977
  correlationId: uuid1,
6978
+ sessionCorrelationId: uuid3,
6881
6979
  joinTrigger: 'jt',
6882
6980
  loginType: 'lt',
6883
6981
  });
6884
6982
  });
6885
6983
 
6886
6984
  it('should update the callState, keeping non-supplied values', () => {
6887
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
6985
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6888
6986
  meeting.updateCallStateForMetrics({joinTrigger: 'jt', loginType: 'lt'});
6889
6987
  assert.deepEqual(meeting.callStateForMetrics, {
6890
6988
  correlationId,
6989
+ sessionCorrelationId: '',
6891
6990
  joinTrigger: 'jt',
6892
6991
  loginType: 'lt',
6893
6992
  });
@@ -8450,6 +8549,9 @@ describe('plugin-meetings', () => {
8450
8549
  it('listens to the self admitted guest event', (done) => {
8451
8550
  meeting.stopKeepAlive = sinon.stub();
8452
8551
  meeting.updateLLMConnection = sinon.stub();
8552
+ meeting.rtcMetrics = {
8553
+ sendNextMetrics: sinon.stub(),
8554
+ };
8453
8555
  meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
8454
8556
  assert.calledOnceWithExactly(meeting.stopKeepAlive);
8455
8557
  assert.calledThrice(TriggerProxy.trigger);
@@ -8461,6 +8563,8 @@ describe('plugin-meetings', () => {
8461
8563
  {payload: test1}
8462
8564
  );
8463
8565
  assert.calledOnce(meeting.updateLLMConnection);
8566
+ assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
8567
+
8464
8568
  done();
8465
8569
  });
8466
8570
 
@@ -1,6 +1,7 @@
1
+ import 'jsdom-global/register';
1
2
  import sinon from 'sinon';
2
3
  import {assert} from '@webex/test-helper-chai';
3
- import { cloneDeep, defer } from 'lodash';
4
+ import { cloneDeep } from 'lodash';
4
5
 
5
6
  import MockWebex from '@webex/test-helper-mock-webex';
6
7
  import Meetings from '@webex/plugin-meetings';
@@ -495,4 +496,4 @@ describe('LocusMediaRequest.send()', () => {
495
496
  });
496
497
 
497
498
  });
498
- })
499
+ })