@webex/plugin-meetings 3.5.0 → 3.6.0-next.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.js +4 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/media/index.js +3 -1
  10. package/dist/media/index.js.map +1 -1
  11. package/dist/meeting/in-meeting-actions.js +3 -1
  12. package/dist/meeting/in-meeting-actions.js.map +1 -1
  13. package/dist/meeting/index.js +39 -4
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/util.js +8 -10
  16. package/dist/meeting/util.js.map +1 -1
  17. package/dist/meetings/index.js +100 -26
  18. package/dist/meetings/index.js.map +1 -1
  19. package/dist/roap/request.js +1 -1
  20. package/dist/roap/request.js.map +1 -1
  21. package/dist/types/config.d.ts +2 -0
  22. package/dist/types/constants.d.ts +2 -1
  23. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  24. package/dist/types/meeting/index.d.ts +11 -0
  25. package/dist/types/meetings/index.d.ts +43 -2
  26. package/dist/webinar/index.js +1 -1
  27. package/package.json +22 -22
  28. package/src/config.ts +3 -0
  29. package/src/constants.ts +1 -0
  30. package/src/media/index.ts +4 -1
  31. package/src/meeting/in-meeting-actions.ts +3 -0
  32. package/src/meeting/index.ts +42 -2
  33. package/src/meeting/util.ts +27 -31
  34. package/src/meetings/index.ts +126 -37
  35. package/src/roap/request.ts +3 -1
  36. package/test/unit/spec/media/index.ts +4 -0
  37. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  38. package/test/unit/spec/meeting/index.js +58 -17
  39. package/test/unit/spec/meeting/utils.js +50 -85
  40. package/test/unit/spec/meetings/index.js +128 -13
  41. package/dist/networkQualityMonitor/index.js +0 -227
  42. package/dist/networkQualityMonitor/index.js.map +0 -1
  43. package/dist/rtcMetrics/constants.js +0 -11
  44. package/dist/rtcMetrics/constants.js.map +0 -1
  45. package/dist/rtcMetrics/index.js +0 -197
  46. package/dist/rtcMetrics/index.js.map +0 -1
  47. package/dist/types/networkQualityMonitor/index.d.ts +0 -70
  48. package/dist/types/rtcMetrics/constants.d.ts +0 -4
  49. package/dist/types/rtcMetrics/index.d.ts +0 -71
  50. package/src/rtcMetrics/constants.ts +0 -3
  51. package/src/rtcMetrics/index.ts +0 -186
  52. package/test/unit/spec/rtcMetrics/index.ts +0 -154
@@ -1,11 +1,12 @@
1
1
  /* eslint no-shadow: ["error", { "allow": ["eventType"] }] */
2
- import {union} from 'lodash';
2
+ import {cloneDeep} from 'lodash';
3
3
  import '@webex/internal-plugin-mercury';
4
4
  import '@webex/internal-plugin-conversation';
5
5
  import '@webex/internal-plugin-metrics';
6
6
  // @ts-ignore
7
7
  import {WebexPlugin} from '@webex/webex-core';
8
8
  import {setLogger} from '@webex/internal-media-core';
9
+ import {DeviceRegistrationOptions} from '@webex/internal-plugin-device';
9
10
 
10
11
  import * as mediaHelpersModule from '@webex/media-helpers';
11
12
 
@@ -133,6 +134,28 @@ class MediaLogger {
133
134
  * @memberof Meetings
134
135
  */
135
136
 
137
+ /**
138
+ * Object containing only the most basic information about a meeting.
139
+ * This is the information that is kept even after the meeting is deleted from the MeetingCollection
140
+ */
141
+ export type BasicMeetingInformation = {
142
+ allowMediaInLobby: boolean;
143
+ correlationId: string;
144
+ environment: string;
145
+ id: string;
146
+ locusUrl: string;
147
+ locusInfo: {
148
+ // it's only a very small subset of the locus info, to avoid using much memory
149
+ url: string;
150
+ fullState: {
151
+ lastActive: string;
152
+ sessionId: string;
153
+ };
154
+ };
155
+ meetingInfo: any;
156
+ sessionCorrelationId: string;
157
+ };
158
+
136
159
  /**
137
160
  * Maintain a cache of meetings and sync with services.
138
161
  * @class
@@ -141,6 +164,7 @@ export default class Meetings extends WebexPlugin {
141
164
  loggerRequest: any;
142
165
  media: any;
143
166
  meetingCollection: any;
167
+ deletedMeetings: Map<string, BasicMeetingInformation>;
144
168
  personalMeetingRoom: any;
145
169
  preferredWebexSite: any;
146
170
  reachability: Reachability;
@@ -191,6 +215,8 @@ export default class Meetings extends WebexPlugin {
191
215
  // @ts-ignore
192
216
  this.loggerRequest = new LoggerRequest({webex: this.webex});
193
217
  this.meetingCollection = new MeetingCollection();
218
+ this.deletedMeetings = new Map();
219
+
194
220
  /**
195
221
  * The PersonalMeetingRoom object to interact with server
196
222
  * @instance
@@ -736,15 +762,35 @@ export default class Meetings extends WebexPlugin {
736
762
  }
737
763
  }
738
764
 
765
+ /**
766
+ * API to toggle backend ipv6 native support config, needs to be called before webex.meetings.register()
767
+ *
768
+ * @param {Boolean} newValue
769
+ * @private
770
+ * @memberof Meetings
771
+ * @returns {undefined}
772
+ */
773
+ private _toggleIpv6BackendNativeSupport(newValue: boolean) {
774
+ if (typeof newValue !== 'boolean') {
775
+ return;
776
+ }
777
+ // @ts-ignore
778
+ if (this.config.backendIpv6NativeSupport !== newValue) {
779
+ // @ts-ignore
780
+ this.config.backendIpv6NativeSupport = newValue;
781
+ }
782
+ }
783
+
739
784
  /**
740
785
  * Explicitly sets up the meetings plugin by registering
741
786
  * the device, connecting to mercury, and listening for locus events.
742
787
  *
788
+ * @param {DeviceRegistrationOptions} [deviceRegistrationOptions] - The options for registering the device (optional)
743
789
  * @returns {Promise}
744
790
  * @public
745
791
  * @memberof Meetings
746
792
  */
747
- public register() {
793
+ public register(deviceRegistrationOptions?: DeviceRegistrationOptions): Promise<any> {
748
794
  // @ts-ignore
749
795
  if (!this.webex.canAuthorize) {
750
796
  LoggerProxy.logger.error(
@@ -770,7 +816,7 @@ export default class Meetings extends WebexPlugin {
770
816
  }),
771
817
  // @ts-ignore
772
818
  this.webex.internal.device
773
- .register()
819
+ .register(deviceRegistrationOptions)
774
820
  // @ts-ignore
775
821
  .then(() =>
776
822
  LoggerProxy.logger.info(
@@ -996,35 +1042,45 @@ export default class Meetings extends WebexPlugin {
996
1042
  * @memberof Meetings
997
1043
  */
998
1044
  fetchUserPreferredWebexSite() {
999
- return this.request.getMeetingPreferences().then((res) => {
1000
- if (res) {
1001
- const preferredWebexSite = MeetingsUtil.parseDefaultSiteFromMeetingPreferences(res);
1002
- this.preferredWebexSite = preferredWebexSite;
1003
- // @ts-ignore
1004
- this.webex.internal.services._getCatalog().addAllowedDomains([preferredWebexSite]);
1005
- }
1045
+ // @ts-ignore
1046
+ return this.webex.people._getMe().then((me) => {
1047
+ const isGuestUser = me.type === 'appuser';
1048
+ if (!isGuestUser) {
1049
+ return this.request.getMeetingPreferences().then((res) => {
1050
+ if (res) {
1051
+ const preferredWebexSite = MeetingsUtil.parseDefaultSiteFromMeetingPreferences(res);
1052
+ this.preferredWebexSite = preferredWebexSite;
1053
+ // @ts-ignore
1054
+ this.webex.internal.services._getCatalog().addAllowedDomains([preferredWebexSite]);
1055
+ }
1006
1056
 
1007
- // fall back to getting the preferred site from the user information
1008
- if (!this.preferredWebexSite) {
1009
- // @ts-ignore
1010
- return this.webex.internal.user
1011
- .get()
1012
- .then((user) => {
1013
- const preferredWebexSite =
1014
- user?.userPreferences?.userPreferencesItems?.preferredWebExSite;
1015
- if (preferredWebexSite) {
1016
- this.preferredWebexSite = preferredWebexSite;
1017
- // @ts-ignore
1018
- this.webex.internal.services._getCatalog().addAllowedDomains([preferredWebexSite]);
1019
- } else {
1020
- throw new Error('site not found');
1021
- }
1022
- })
1023
- .catch(() => {
1024
- LoggerProxy.logger.error(
1025
- 'Failed to fetch preferred site from user - no site will be set'
1026
- );
1027
- });
1057
+ // fall back to getting the preferred site from the user information
1058
+ if (!this.preferredWebexSite) {
1059
+ // @ts-ignore
1060
+ return this.webex.internal.user
1061
+ .get()
1062
+ .then((user) => {
1063
+ const preferredWebexSite =
1064
+ user?.userPreferences?.userPreferencesItems?.preferredWebExSite;
1065
+ if (preferredWebexSite) {
1066
+ this.preferredWebexSite = preferredWebexSite;
1067
+ // @ts-ignore
1068
+ this.webex.internal.services
1069
+ ._getCatalog()
1070
+ .addAllowedDomains([preferredWebexSite]);
1071
+ } else {
1072
+ throw new Error('site not found');
1073
+ }
1074
+ })
1075
+ .catch(() => {
1076
+ LoggerProxy.logger.error(
1077
+ 'Failed to fetch preferred site from user - no site will be set'
1078
+ );
1079
+ });
1080
+ }
1081
+
1082
+ return Promise.resolve();
1083
+ });
1028
1084
  }
1029
1085
 
1030
1086
  return Promise.resolve();
@@ -1037,11 +1093,21 @@ export default class Meetings extends WebexPlugin {
1037
1093
  * @public
1038
1094
  * @memberof Meetings
1039
1095
  */
1040
-
1041
1096
  getPersonalMeetingRoom() {
1042
1097
  return this.personalMeetingRoom;
1043
1098
  }
1044
1099
 
1100
+ /**
1101
+ * Returns basic information about a meeting that exists or
1102
+ * used to exist in the MeetingCollection
1103
+ *
1104
+ * @param {string} meetingId
1105
+ * @returns {BasicMeetingInformation|undefined}
1106
+ */
1107
+ public getBasicMeetingInformation(meetingId: string): BasicMeetingInformation {
1108
+ return this.meetingCollection.get(meetingId) || this.deletedMeetings.get(meetingId);
1109
+ }
1110
+
1045
1111
  /**
1046
1112
  * @param {Meeting} meeting
1047
1113
  * @param {Object} reason
@@ -1052,6 +1118,24 @@ export default class Meetings extends WebexPlugin {
1052
1118
  */
1053
1119
  private destroy(meeting: Meeting, reason: object) {
1054
1120
  MeetingUtil.cleanUp(meeting);
1121
+ // keep some basic info about the deleted meeting forever
1122
+ this.deletedMeetings.set(meeting.id, {
1123
+ id: meeting.id,
1124
+ allowMediaInLobby: meeting.allowMediaInLobby,
1125
+ correlationId: meeting.correlationId,
1126
+ sessionCorrelationId: meeting.sessionCorrelationId,
1127
+ environment: meeting.environment,
1128
+ locusUrl: meeting.locusUrl,
1129
+ meetingInfo: cloneDeep(meeting.meetingInfo),
1130
+ locusInfo: {
1131
+ // locusInfo can be quite big, so keep just the minimal info
1132
+ url: meeting.locusInfo?.url,
1133
+ fullState: {
1134
+ lastActive: meeting.locusInfo?.fullState?.lastActive,
1135
+ sessionId: meeting.locusInfo?.fullState?.sessionId,
1136
+ },
1137
+ },
1138
+ });
1055
1139
  this.meetingCollection.delete(meeting.id);
1056
1140
  Trigger.trigger(
1057
1141
  this,
@@ -1081,6 +1165,7 @@ export default class Meetings extends WebexPlugin {
1081
1165
  * @param {CallStateForMetrics} callStateForMetrics - information about call state for metrics
1082
1166
  * @param {Object} [meetingInfo] - Pre-fetched complete meeting info
1083
1167
  * @param {String} [meetingLookupUrl] - meeting info prefetch url
1168
+ * @param {string} sessionCorrelationId - the optional specified sessionCorrelationId (callStateForMetrics.sessionCorrelationId) can be provided instead
1084
1169
  * @returns {Promise<Meeting>} A new Meeting.
1085
1170
  * @public
1086
1171
  * @memberof Meetings
@@ -1094,7 +1179,8 @@ export default class Meetings extends WebexPlugin {
1094
1179
  failOnMissingMeetingInfo = false,
1095
1180
  callStateForMetrics: CallStateForMetrics = undefined,
1096
1181
  meetingInfo = undefined,
1097
- meetingLookupUrl = undefined
1182
+ meetingLookupUrl = undefined,
1183
+ sessionCorrelationId: string = undefined
1098
1184
  ) {
1099
1185
  // Validate meeting information based on the provided destination and
1100
1186
  // type. This must be performed prior to determining if the meeting is
@@ -1105,6 +1191,10 @@ export default class Meetings extends WebexPlugin {
1105
1191
  callStateForMetrics = {...(callStateForMetrics || {}), correlationId};
1106
1192
  }
1107
1193
 
1194
+ if (sessionCorrelationId) {
1195
+ callStateForMetrics = {...(callStateForMetrics || {}), sessionCorrelationId};
1196
+ }
1197
+
1108
1198
  return (
1109
1199
  this.meetingInfo
1110
1200
  .fetchInfoOptions(destination, type)
@@ -1176,10 +1266,9 @@ export default class Meetings extends WebexPlugin {
1176
1266
  locusId: createdMeeting.locusId,
1177
1267
  meetingId: createdMeeting.locusInfo?.info?.webExMeetingId,
1178
1268
  autoupload: true,
1179
- }).then(() => this.destroy(createdMeeting, payload.reason));
1180
- } else {
1181
- this.destroy(createdMeeting, payload.reason);
1269
+ });
1182
1270
  }
1271
+ this.destroy(createdMeeting, payload.reason);
1183
1272
  });
1184
1273
 
1185
1274
  createdMeeting.on(EVENTS.REQUEST_UPLOAD_LOGS, (meetingInstance) => {
@@ -1206,7 +1295,7 @@ export default class Meetings extends WebexPlugin {
1206
1295
  return Promise.resolve(createdMeeting);
1207
1296
  });
1208
1297
  }
1209
- meeting.setCallStateForMetrics(callStateForMetrics);
1298
+ meeting.updateCallStateForMetrics(callStateForMetrics);
1210
1299
 
1211
1300
  // Return the existing meeting.
1212
1301
  return Promise.resolve(meeting);
@@ -79,7 +79,9 @@ export default class RoapRequest extends StatelessWebexPlugin {
79
79
  });
80
80
 
81
81
  LoggerProxy.logger.info(
82
- `Roap:request#sendRoap --> ${locusSelfUrl} \n ${roapMessage.messageType} \n seq:${roapMessage.seq}`
82
+ `Roap:request#sendRoap --> ${roapMessage.messageType} seq:${roapMessage.seq} ${
83
+ ipVersion ? `ipver=${ipVersion} ` : ''
84
+ } ${locusSelfUrl}`
83
85
  );
84
86
 
85
87
  return locusMediaRequest
@@ -83,6 +83,7 @@ describe('createMediaConnection', () => {
83
83
  username: 'turn username',
84
84
  password: 'turn password',
85
85
  },
86
+ iceCandidatesTimeout: undefined,
86
87
  });
87
88
  assert.calledOnce(roapMediaConnectionConstructorStub);
88
89
  assert.calledWith(
@@ -100,6 +101,7 @@ describe('createMediaConnection', () => {
100
101
  credential: 'turn password',
101
102
  },
102
103
  ],
104
+ iceCandidatesTimeout: undefined,
103
105
  skipInactiveTransceivers: false,
104
106
  requireH264: true,
105
107
  sdpMunging: {
@@ -306,12 +308,14 @@ describe('createMediaConnection', () => {
306
308
  enableRtx: ENABLE_RTX,
307
309
  enableExtmap: ENABLE_EXTMAP,
308
310
  turnServerInfo,
311
+ iceCandidatesTimeout: undefined,
309
312
  });
310
313
  assert.calledOnce(roapMediaConnectionConstructorStub);
311
314
  assert.calledWith(
312
315
  roapMediaConnectionConstructorStub,
313
316
  {
314
317
  iceServers: [],
318
+ iceCandidatesTimeout: undefined,
315
319
  skipInactiveTransceivers: false,
316
320
  requireH264: true,
317
321
  sdpMunging: {
@@ -78,6 +78,7 @@ describe('plugin-meetings', () => {
78
78
  supportHDV: null,
79
79
  canShareWhiteBoard: null,
80
80
  enforceVirtualBackground: null,
81
+ canPollingAndQA: null,
81
82
  ...expected,
82
83
  };
83
84
 
@@ -161,6 +162,7 @@ describe('plugin-meetings', () => {
161
162
  'supportHDV',
162
163
  'canShareWhiteBoard',
163
164
  'enforceVirtualBackground',
165
+ 'canPollingAndQA',
164
166
  ].forEach((key) => {
165
167
  it(`get and set for ${key} work as expected`, () => {
166
168
  const inMeetingActions = new InMeetingActions();
@@ -5,7 +5,6 @@ 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
8
  import * as RemoteMediaManagerModule from '@webex/plugin-meetings/src/multistream/remoteMediaManager';
10
9
  import StateMachine from 'javascript-state-machine';
11
10
  import uuid from 'uuid';
@@ -307,7 +306,7 @@ describe('plugin-meetings', () => {
307
306
  assert.equal(meeting.resource, uuid2);
308
307
  assert.equal(meeting.deviceUrl, uuid3);
309
308
  assert.equal(meeting.correlationId, correlationId);
310
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
309
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
311
310
  assert.deepEqual(meeting.meetingInfo, {});
312
311
  assert.instanceOf(meeting.members, Members);
313
312
  assert.calledOnceWithExactly(
@@ -376,7 +375,7 @@ describe('plugin-meetings', () => {
376
375
  }
377
376
  );
378
377
  assert.equal(newMeeting.correlationId, newMeeting.id);
379
- assert.deepEqual(newMeeting.callStateForMetrics, {correlationId: newMeeting.id});
378
+ assert.deepEqual(newMeeting.callStateForMetrics, {correlationId: newMeeting.id, sessionCorrelationId: ''});
380
379
  });
381
380
 
382
381
  it('correlationId can be provided in callStateForMetrics', () => {
@@ -403,6 +402,37 @@ describe('plugin-meetings', () => {
403
402
  correlationId: uuid4,
404
403
  joinTrigger: 'fake-join-trigger',
405
404
  loginType: 'fake-login-type',
405
+ sessionCorrelationId: '',
406
+ });
407
+ });
408
+
409
+ it('sessionCorrelationId can be provided in callStateForMetrics', () => {
410
+ const newMeeting = new Meeting(
411
+ {
412
+ userId: uuid1,
413
+ resource: uuid2,
414
+ deviceUrl: uuid3,
415
+ locus: {url: url1},
416
+ destination: testDestination,
417
+ destinationType: DESTINATION_TYPE.MEETING_ID,
418
+ callStateForMetrics: {
419
+ correlationId: uuid4,
420
+ sessionCorrelationId: uuid1,
421
+ joinTrigger: 'fake-join-trigger',
422
+ loginType: 'fake-login-type',
423
+ },
424
+ },
425
+ {
426
+ parent: webex,
427
+ }
428
+ );
429
+ assert.exists(newMeeting.sessionCorrelationId);
430
+ assert.equal(newMeeting.sessionCorrelationId, uuid1);
431
+ assert.deepEqual(newMeeting.callStateForMetrics, {
432
+ correlationId: uuid4,
433
+ sessionCorrelationId: uuid1,
434
+ joinTrigger: 'fake-join-trigger',
435
+ loginType: 'fake-login-type',
406
436
  });
407
437
  });
408
438
 
@@ -1611,7 +1641,6 @@ describe('plugin-meetings', () => {
1611
1641
 
1612
1642
  assert.calledOnce(MeetingUtil.joinMeeting);
1613
1643
  assert.calledOnce(webex.internal.device.meetingStarted);
1614
- assert.calledOnce(meeting.setLocus);
1615
1644
  assert.equal(result, joinMeetingResult);
1616
1645
  assert.calledWith(webex.internal.llm.on, 'online', meeting.handleLLMOnline);
1617
1646
  });
@@ -2456,8 +2485,8 @@ describe('plugin-meetings', () => {
2456
2485
  });
2457
2486
 
2458
2487
  it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
2459
- const fakeRtcMetrics = {id: 'fake rtc metrics object'};
2460
- const rtcMetricsCtor = sinon.stub(RtcMetricsModule, 'default').returns(fakeRtcMetrics);
2488
+ const setIntervalOriginal = window.setInterval;
2489
+ window.setInterval = sinon.stub().returns(1);
2461
2490
 
2462
2491
  // setup the minimum mocks required for multistream connection
2463
2492
  fakeMediaConnection.createSendSlot = sinon.stub().returns({
@@ -2478,8 +2507,6 @@ describe('plugin-meetings', () => {
2478
2507
  mediaSettings: {},
2479
2508
  });
2480
2509
 
2481
- assert.calledOnceWithExactly(rtcMetricsCtor, webex, meeting.id, meeting.correlationId);
2482
-
2483
2510
  // check that rtcMetrics was passed to Media.createMediaConnection
2484
2511
  assert.calledOnce(Media.createMediaConnection);
2485
2512
  assert.calledWith(
@@ -2487,10 +2514,10 @@ describe('plugin-meetings', () => {
2487
2514
  true,
2488
2515
  meeting.getMediaConnectionDebugId(),
2489
2516
  meeting.id,
2490
- sinon.match({
2491
- rtcMetrics: fakeRtcMetrics,
2492
- })
2517
+ sinon.match.hasNested('rtcMetrics.webex', webex)
2493
2518
  );
2519
+
2520
+ window.setInterval = setIntervalOriginal;
2494
2521
  });
2495
2522
 
2496
2523
  it('should pass the turn server info to the peer connection', async () => {
@@ -3677,6 +3704,7 @@ describe('plugin-meetings', () => {
3677
3704
  credential: 'turn password',
3678
3705
  },
3679
3706
  ],
3707
+ iceCandidatesTimeout: meeting.config.iceCandidatesGatheringTimeout,
3680
3708
  skipInactiveTransceivers: false,
3681
3709
  requireH264: true,
3682
3710
  sdpMunging: {
@@ -3761,11 +3789,16 @@ describe('plugin-meetings', () => {
3761
3789
  // that's being tested in these tests)
3762
3790
  meeting.webex.meetings.registered = true;
3763
3791
  meeting.webex.internal.device.config = {};
3764
- sinon.stub(MeetingUtil, 'joinMeeting').resolves({
3792
+ sinon.stub(MeetingUtil, 'parseLocusJoin').returns({
3765
3793
  id: 'fake locus from mocked join request',
3766
3794
  locusUrl: 'fake locus url',
3767
3795
  mediaId: 'fake media id',
3768
- });
3796
+ })
3797
+ sinon.stub(meeting.meetingRequest, 'joinMeeting').resolves({
3798
+ headers: {
3799
+ trackingid: 'fake tracking id',
3800
+ }
3801
+ })
3769
3802
  await meeting.join({enableMultistream: isMultistream});
3770
3803
  });
3771
3804
 
@@ -6927,33 +6960,36 @@ describe('plugin-meetings', () => {
6927
6960
  describe('#setCorrelationId', () => {
6928
6961
  it('should set the correlationId and return undefined', () => {
6929
6962
  assert.equal(meeting.correlationId, correlationId);
6930
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
6963
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6931
6964
  meeting.setCorrelationId(uuid1);
6932
6965
  assert.equal(meeting.correlationId, uuid1);
6933
- assert.deepEqual(meeting.callStateForMetrics, {correlationId: uuid1});
6966
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId: uuid1, sessionCorrelationId: ''});
6934
6967
  });
6935
6968
  });
6936
6969
 
6937
6970
  describe('#updateCallStateForMetrics', () => {
6938
6971
  it('should update the callState, overriding existing values', () => {
6939
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
6972
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6940
6973
  meeting.updateCallStateForMetrics({
6941
6974
  correlationId: uuid1,
6975
+ sessionCorrelationId: uuid3,
6942
6976
  joinTrigger: 'jt',
6943
6977
  loginType: 'lt',
6944
6978
  });
6945
6979
  assert.deepEqual(meeting.callStateForMetrics, {
6946
6980
  correlationId: uuid1,
6981
+ sessionCorrelationId: uuid3,
6947
6982
  joinTrigger: 'jt',
6948
6983
  loginType: 'lt',
6949
6984
  });
6950
6985
  });
6951
6986
 
6952
6987
  it('should update the callState, keeping non-supplied values', () => {
6953
- assert.deepEqual(meeting.callStateForMetrics, {correlationId});
6988
+ assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''});
6954
6989
  meeting.updateCallStateForMetrics({joinTrigger: 'jt', loginType: 'lt'});
6955
6990
  assert.deepEqual(meeting.callStateForMetrics, {
6956
6991
  correlationId,
6992
+ sessionCorrelationId: '',
6957
6993
  joinTrigger: 'jt',
6958
6994
  loginType: 'lt',
6959
6995
  });
@@ -9837,6 +9873,11 @@ describe('plugin-meetings', () => {
9837
9873
  requiredDisplayHints: [],
9838
9874
  requiredPolicies: [SELF_POLICY.SUPPORT_ANNOTATION],
9839
9875
  },
9876
+ {
9877
+ actionName: 'canPollingAndQA',
9878
+ requiredDisplayHints: [],
9879
+ requiredPolicies: [SELF_POLICY.SUPPORT_POLLING_AND_QA],
9880
+ },
9840
9881
  ],
9841
9882
  ({
9842
9883
  actionName,