@webex/plugin-meetings 3.7.0-next.2 → 3.7.0-next.20

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 (82) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
  4. package/dist/common/errors/join-webinar-error.js.map +1 -0
  5. package/dist/config.js +1 -1
  6. package/dist/config.js.map +1 -1
  7. package/dist/constants.js +27 -6
  8. package/dist/constants.js.map +1 -1
  9. package/dist/index.js +8 -15
  10. package/dist/index.js.map +1 -1
  11. package/dist/interpretation/index.js +1 -1
  12. package/dist/interpretation/siLanguage.js +1 -1
  13. package/dist/meeting/in-meeting-actions.js +11 -1
  14. package/dist/meeting/in-meeting-actions.js.map +1 -1
  15. package/dist/meeting/index.js +84 -131
  16. package/dist/meeting/index.js.map +1 -1
  17. package/dist/meeting/util.js +0 -8
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/meeting-info/meeting-info-v2.js +29 -17
  20. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  21. package/dist/meetings/index.js +6 -3
  22. package/dist/meetings/index.js.map +1 -1
  23. package/dist/members/util.js +4 -2
  24. package/dist/members/util.js.map +1 -1
  25. package/dist/metrics/constants.js +3 -1
  26. package/dist/metrics/constants.js.map +1 -1
  27. package/dist/multistream/remoteMedia.js +30 -15
  28. package/dist/multistream/remoteMedia.js.map +1 -1
  29. package/dist/reachability/clusterReachability.js +12 -11
  30. package/dist/reachability/clusterReachability.js.map +1 -1
  31. package/dist/recording-controller/enums.js +8 -4
  32. package/dist/recording-controller/enums.js.map +1 -1
  33. package/dist/recording-controller/index.js +18 -9
  34. package/dist/recording-controller/index.js.map +1 -1
  35. package/dist/recording-controller/util.js +13 -9
  36. package/dist/recording-controller/util.js.map +1 -1
  37. package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
  38. package/dist/types/constants.d.ts +19 -1
  39. package/dist/types/index.d.ts +3 -3
  40. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  41. package/dist/types/meeting/index.d.ts +1 -10
  42. package/dist/types/meeting/util.d.ts +0 -1
  43. package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
  44. package/dist/types/meetings/index.d.ts +3 -0
  45. package/dist/types/members/util.d.ts +2 -0
  46. package/dist/types/metrics/constants.d.ts +3 -1
  47. package/dist/types/recording-controller/enums.d.ts +5 -2
  48. package/dist/types/recording-controller/index.d.ts +1 -0
  49. package/dist/types/recording-controller/util.d.ts +2 -1
  50. package/dist/webinar/index.js +357 -7
  51. package/dist/webinar/index.js.map +1 -1
  52. package/package.json +22 -22
  53. package/src/common/errors/join-webinar-error.ts +24 -0
  54. package/src/config.ts +1 -1
  55. package/src/constants.ts +24 -3
  56. package/src/index.ts +2 -3
  57. package/src/meeting/in-meeting-actions.ts +21 -0
  58. package/src/meeting/index.ts +54 -48
  59. package/src/meeting/util.ts +0 -9
  60. package/src/meeting-info/meeting-info-v2.ts +23 -11
  61. package/src/meetings/index.ts +8 -2
  62. package/src/members/util.ts +1 -0
  63. package/src/metrics/constants.ts +3 -1
  64. package/src/multistream/remoteMedia.ts +28 -15
  65. package/src/reachability/clusterReachability.ts +4 -1
  66. package/src/recording-controller/enums.ts +5 -2
  67. package/src/recording-controller/index.ts +17 -4
  68. package/src/recording-controller/util.ts +20 -5
  69. package/src/webinar/index.ts +201 -9
  70. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -1
  71. package/test/unit/spec/meeting/index.js +106 -77
  72. package/test/unit/spec/meeting/utils.js +0 -15
  73. package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
  74. package/test/unit/spec/meetings/index.js +9 -5
  75. package/test/unit/spec/members/utils.js +95 -0
  76. package/test/unit/spec/multistream/remoteMedia.ts +11 -7
  77. package/test/unit/spec/reachability/clusterReachability.ts +7 -0
  78. package/test/unit/spec/recording-controller/index.js +61 -5
  79. package/test/unit/spec/recording-controller/util.js +39 -3
  80. package/test/unit/spec/webinar/index.ts +363 -0
  81. package/dist/common/errors/webinar-registration-error.js.map +0 -1
  82. package/src/common/errors/webinar-registration-error.ts +0 -27
@@ -1,11 +1,13 @@
1
1
  /*!
2
2
  * Copyright (c) 2015-2023 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
- import {WebexPlugin} from '@webex/webex-core';
4
+ import {WebexPlugin, config} from '@webex/webex-core';
5
+ import uuid from 'uuid';
5
6
  import {get} from 'lodash';
6
- import {MEETINGS, SELF_ROLES} from '../constants';
7
+ import {HEADERS, HTTP_VERBS, MEETINGS, SELF_ROLES} from '../constants';
7
8
 
8
9
  import WebinarCollection from './collection';
10
+ import LoggerProxy from '../common/logs/logger-proxy';
9
11
 
10
12
  /**
11
13
  * @class Webinar
@@ -22,6 +24,7 @@ const Webinar = WebexPlugin.extend({
22
24
  canManageWebcast: 'boolean', // appears the ability to manage webcast
23
25
  selfIsPanelist: 'boolean', // self is panelist
24
26
  selfIsAttendee: 'boolean', // self is attendee
27
+ practiceSessionEnabled: 'boolean', // practice session enabled
25
28
  },
26
29
 
27
30
  /**
@@ -59,18 +62,207 @@ const Webinar = WebexPlugin.extend({
59
62
  * @returns {{isPromoted: boolean, isDemoted: boolean}} Role transition states
60
63
  */
61
64
  updateRoleChanged(payload) {
65
+ const oldRoles = get(payload, 'oldRoles', []);
66
+ const newRoles = get(payload, 'newRoles', []);
67
+
62
68
  const isPromoted =
63
- get(payload, 'oldRoles', []).includes(SELF_ROLES.ATTENDEE) &&
64
- get(payload, 'newRoles', []).includes(SELF_ROLES.PANELIST);
69
+ oldRoles.includes(SELF_ROLES.ATTENDEE) && newRoles.includes(SELF_ROLES.PANELIST);
65
70
  const isDemoted =
66
- get(payload, 'oldRoles', []).includes(SELF_ROLES.PANELIST) &&
67
- get(payload, 'newRoles', []).includes(SELF_ROLES.ATTENDEE);
68
- this.set('selfIsPanelist', get(payload, 'newRoles', []).includes(SELF_ROLES.PANELIST));
69
- this.set('selfIsAttendee', get(payload, 'newRoles', []).includes(SELF_ROLES.ATTENDEE));
70
- this.updateCanManageWebcast(get(payload, 'newRoles', []).includes(SELF_ROLES.MODERATOR));
71
+ oldRoles.includes(SELF_ROLES.PANELIST) && newRoles.includes(SELF_ROLES.ATTENDEE);
72
+ this.set('selfIsPanelist', newRoles.includes(SELF_ROLES.PANELIST));
73
+ this.set('selfIsAttendee', newRoles.includes(SELF_ROLES.ATTENDEE));
74
+ this.updateCanManageWebcast(newRoles.includes(SELF_ROLES.MODERATOR));
71
75
 
72
76
  return {isPromoted, isDemoted};
73
77
  },
78
+
79
+ /**
80
+ * start or stop practice session for webinar
81
+ * @param {boolean} enabled
82
+ * @returns {Promise}
83
+ */
84
+ setPracticeSessionState(enabled) {
85
+ return this.request({
86
+ method: HTTP_VERBS.PATCH,
87
+ uri: `${this.locusUrl}/controls`,
88
+ body: {
89
+ practiceSession: {
90
+ enabled,
91
+ },
92
+ },
93
+ }).catch((error) => {
94
+ LoggerProxy.logger.error('Meeting:webinar#setPracticeSessionState failed', error);
95
+ throw error;
96
+ });
97
+ },
98
+
99
+ /**
100
+ * update practice session status
101
+ * @param {object} payload
102
+ * @returns {void}
103
+ */
104
+ updatePracticeSessionStatus(payload) {
105
+ this.set('practiceSessionEnabled', payload.enabled);
106
+ },
107
+
108
+ /**
109
+ * start webcast mode for webinar
110
+ * @param {object} meeting
111
+ * @param {object} layout
112
+ * @returns {Promise}
113
+ */
114
+ async startWebcast(meeting, layout) {
115
+ if (!meeting) {
116
+ LoggerProxy.logger.error(
117
+ `Meeting:webinar#startWebcast failed --> meeting parameter : ${meeting}`
118
+ );
119
+ throw new Error('Meeting parameter does not meet expectations');
120
+ }
121
+
122
+ return this.request({
123
+ method: HTTP_VERBS.PUT,
124
+ uri: `${this.webcastInstanceUrl}/streaming`,
125
+ headers: {
126
+ authorization: await this.webex.credentials.getUserToken(),
127
+ trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
128
+ [HEADERS.CONTENT_TYPE]: HEADERS.CONTENT_TYPE_VALUE.APPLICATION_JSON,
129
+ },
130
+ body: {
131
+ action: 'start',
132
+ meetingInfo: {
133
+ locusId: meeting.locusId,
134
+ correlationId: meeting.correlationId,
135
+ },
136
+ layout,
137
+ },
138
+ }).catch((error) => {
139
+ LoggerProxy.logger.error('Meeting:webinar#startWebcast failed', error);
140
+ throw error;
141
+ });
142
+ },
143
+
144
+ /**
145
+ * stop webcast mode for webinar
146
+ * @returns {Promise}
147
+ */
148
+ async stopWebcast() {
149
+ return this.request({
150
+ method: HTTP_VERBS.PUT,
151
+ uri: `${this.webcastInstanceUrl}/streaming`,
152
+ headers: {
153
+ authorization: await this.webex.credentials.getUserToken(),
154
+ trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
155
+ [HEADERS.CONTENT_TYPE]: HEADERS.CONTENT_TYPE_VALUE.APPLICATION_JSON,
156
+ },
157
+ body: {
158
+ action: 'stop',
159
+ },
160
+ }).catch((error) => {
161
+ LoggerProxy.logger.error('Meeting:webinar#stopWebcast failed', error);
162
+ throw error;
163
+ });
164
+ },
165
+
166
+ /**
167
+ * query webcast layout for webinar
168
+ * @returns {Promise}
169
+ */
170
+ async queryWebcastLayout() {
171
+ return this.request({
172
+ method: HTTP_VERBS.GET,
173
+ uri: `${this.webcastInstanceUrl}/layout`,
174
+ headers: {
175
+ authorization: await this.webex.credentials.getUserToken(),
176
+ trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
177
+ },
178
+ }).catch((error) => {
179
+ LoggerProxy.logger.error('Meeting:webinar#queryWebcastLayout failed', error);
180
+ throw error;
181
+ });
182
+ },
183
+
184
+ /**
185
+ * update webcast layout for webinar
186
+ * @param {object} layout
187
+ * @returns {Promise}
188
+ */
189
+ async updateWebcastLayout(layout) {
190
+ return this.request({
191
+ method: HTTP_VERBS.PUT,
192
+ uri: `${this.webcastInstanceUrl}/layout`,
193
+ headers: {
194
+ authorization: await this.webex.credentials.getUserToken(),
195
+ trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
196
+ [HEADERS.CONTENT_TYPE]: HEADERS.CONTENT_TYPE_VALUE.APPLICATION_JSON,
197
+ },
198
+ body: {
199
+ videoLayout: layout.videoLayout,
200
+ contentLayout: layout.contentLayout,
201
+ syncStageLayout: layout.syncStageLayout,
202
+ syncStageInMeeting: layout.syncStageInMeeting,
203
+ },
204
+ }).catch((error) => {
205
+ LoggerProxy.logger.error('Meeting:webinar#updateWebcastLayout failed', error);
206
+ throw error;
207
+ });
208
+ },
209
+
210
+ /**
211
+ * view all webcast attendees
212
+ * @param {string} queryString
213
+ * @returns {Promise}
214
+ */
215
+ async viewAllWebcastAttendees() {
216
+ return this.request({
217
+ method: HTTP_VERBS.GET,
218
+ uri: `${this.webcastInstanceUrl}/attendees`,
219
+ headers: {
220
+ authorization: await this.webex.credentials.getUserToken(),
221
+ trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
222
+ },
223
+ }).catch((error) => {
224
+ LoggerProxy.logger.error('Meeting:webinar#viewAllWebcastAttendees failed', error);
225
+ throw error;
226
+ });
227
+ },
228
+
229
+ /**
230
+ * search webcast attendees by query string
231
+ * @param {string} queryString
232
+ * @returns {Promise}
233
+ */
234
+ async searchWebcastAttendees(queryString = '') {
235
+ return this.request({
236
+ method: HTTP_VERBS.GET,
237
+ uri: `${this.webcastInstanceUrl}/attendees?keyword=${encodeURIComponent(queryString)}`,
238
+ headers: {
239
+ authorization: await this.webex.credentials.getUserToken(),
240
+ trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
241
+ },
242
+ }).catch((error) => {
243
+ LoggerProxy.logger.error('Meeting:webinar#searchWebcastAttendees failed', error);
244
+ throw error;
245
+ });
246
+ },
247
+
248
+ /**
249
+ * expel webcast attendee by participantId
250
+ * @param {string} participantId
251
+ * @returns {Promise}
252
+ */
253
+ async expelWebcastAttendee(participantId) {
254
+ return this.request({
255
+ method: HTTP_VERBS.DELETE,
256
+ uri: `${this.webcastInstanceUrl}/attendees/${participantId}`,
257
+ headers: {
258
+ authorization: await this.webex.credentials.getUserToken(),
259
+ trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
260
+ },
261
+ }).catch((error) => {
262
+ LoggerProxy.logger.error('Meeting:webinar#expelWebcastAttendee failed', error);
263
+ throw error;
264
+ });
265
+ },
74
266
  });
75
267
 
76
268
  export default Webinar;
@@ -33,6 +33,7 @@ describe('plugin-meetings', () => {
33
33
  canStartManualCaption: null,
34
34
  canStopManualCaption: null,
35
35
  isManualCaptionActive: null,
36
+ isPremiseRecordingEnabled: null,
36
37
  isSaveTranscriptsEnabled: null,
37
38
  isWebexAssistantActive: null,
38
39
  canViewCaptionPanel: null,
@@ -88,6 +89,11 @@ describe('plugin-meetings', () => {
88
89
  canShowStageView: null,
89
90
  canEnableStageView: null,
90
91
  canDisableStageView: null,
92
+ isPracticeSessionOn : null,
93
+ isPracticeSessionOff : null,
94
+ canStartPracticeSession: null,
95
+ canStopPracticeSession: null,
96
+
91
97
  ...expected,
92
98
  };
93
99
 
@@ -126,6 +132,7 @@ describe('plugin-meetings', () => {
126
132
  'canStartManualCaption',
127
133
  'canStopManualCaption',
128
134
  'isManualCaptionActive',
135
+ 'isPremiseRecordingEnabled',
129
136
  'isSaveTranscriptsEnabled',
130
137
  'isWebexAssistantActive',
131
138
  'canViewCaptionPanel',
@@ -181,7 +188,12 @@ describe('plugin-meetings', () => {
181
188
  'canShowStageView',
182
189
  'canEnableStageView',
183
190
  'canDisableStageView',
184
- ].forEach((key) => {
191
+ 'isPracticeSessionOn',
192
+ 'isPracticeSessionOff',
193
+ 'canStartPracticeSession',
194
+ 'canStopPracticeSession',
195
+
196
+ ].forEach((key) => {
185
197
  it(`get and set for ${key} work as expected`, () => {
186
198
  const inMeetingActions = new InMeetingActions();
187
199
 
@@ -91,14 +91,14 @@ import ParameterError from '../../../../src/common/errors/parameter';
91
91
  import PasswordError from '../../../../src/common/errors/password-error';
92
92
  import CaptchaError from '../../../../src/common/errors/captcha-error';
93
93
  import PermissionError from '../../../../src/common/errors/permission';
94
- import WebinarRegistrationError from '../../../../src/common/errors/webinar-registration-error';
94
+ import JoinWebinarError from '../../../../src/common/errors/join-webinar-error';
95
95
  import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
96
96
  import testUtils from '../../../utils/testUtils';
97
97
  import {
98
98
  MeetingInfoV2CaptchaError,
99
99
  MeetingInfoV2PasswordError,
100
100
  MeetingInfoV2PolicyError,
101
- MeetingInfoV2WebinarRegistrationError,
101
+ MeetingInfoV2JoinWebinarError,
102
102
  } from '../../../../src/meeting-info/meeting-info-v2';
103
103
  import {
104
104
  DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
@@ -1705,6 +1705,12 @@ describe('plugin-meetings', () => {
1705
1705
  sinon.assert.called(setCorrelationIdSpy);
1706
1706
  assert.equal(meeting.correlationId, '123');
1707
1707
  });
1708
+
1709
+ it('should not send client.call.initiated if told not to', async () => {
1710
+ await meeting.join({sendCallInitiated: false});
1711
+
1712
+ sinon.assert.notCalled(webex.internal.newMetrics.submitClientEvent);
1713
+ });
1708
1714
  });
1709
1715
 
1710
1716
  describe('failure', () => {
@@ -2492,9 +2498,11 @@ describe('plugin-meetings', () => {
2492
2498
  mediaSettings: {},
2493
2499
  });
2494
2500
 
2495
- const checkLogCounter = (delay, expectedCounter) => {
2501
+ const checkLogCounter = (delayInMinutes, expectedCounter) => {
2502
+ const delayInMilliseconds = delayInMinutes * 60 * 1000;
2503
+
2496
2504
  // first check that the counter is not increased just before the delay
2497
- clock.tick(delay - 50);
2505
+ clock.tick(delayInMilliseconds - 50);
2498
2506
  assert.equal(logUploadCounter, expectedCounter - 1);
2499
2507
 
2500
2508
  // and now check that it has reached expected value after the delay
@@ -2502,22 +2510,18 @@ describe('plugin-meetings', () => {
2502
2510
  assert.equal(logUploadCounter, expectedCounter);
2503
2511
  };
2504
2512
 
2505
- checkLogCounter(100, 1);
2506
- checkLogCounter(1000, 2);
2507
- checkLogCounter(15000, 3);
2508
- checkLogCounter(15000, 4);
2509
- checkLogCounter(30000, 5);
2510
- checkLogCounter(30000, 6);
2511
- checkLogCounter(30000, 7);
2512
- checkLogCounter(60000, 8);
2513
- checkLogCounter(60000, 9);
2514
- checkLogCounter(60000, 10);
2513
+ checkLogCounter(0.1, 1);
2514
+ checkLogCounter(15, 2);
2515
+ checkLogCounter(30, 3);
2516
+ checkLogCounter(60, 4);
2517
+ checkLogCounter(60, 5);
2515
2518
 
2516
- // simulate media connection being removed -> no more log uploads should happen
2519
+ // simulate media connection being removed -> 1 more upload should happen, but nothing more afterwards
2517
2520
  meeting.mediaProperties.webrtcMediaConnection = undefined;
2521
+ checkLogCounter(60, 6);
2518
2522
 
2519
- clock.tick(60000);
2520
- assert.equal(logUploadCounter, 11);
2523
+ clock.tick(120 * 1000 * 60);
2524
+ assert.equal(logUploadCounter, 6);
2521
2525
 
2522
2526
  clock.restore();
2523
2527
  });
@@ -3552,14 +3556,6 @@ describe('plugin-meetings', () => {
3552
3556
  });
3553
3557
  });
3554
3558
 
3555
- it('succeeds even if getDevices() throws', async () => {
3556
- meeting.meetingState = 'ACTIVE';
3557
-
3558
- sinon.stub(InternalMediaCoreModule, 'getDevices').rejects(new Error('fake error'));
3559
-
3560
- await meeting.addMedia();
3561
- });
3562
-
3563
3559
  describe('CA ice failures checks', () => {
3564
3560
  [
3565
3561
  {
@@ -3743,8 +3739,12 @@ describe('plugin-meetings', () => {
3743
3739
  meeting.setMercuryListener = sinon.stub();
3744
3740
  meeting.locusInfo.onFullLocus = sinon.stub();
3745
3741
  meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
3746
- meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon.stub().resolves({id: 'fake reachability'});
3747
- meeting.webex.meetings.reachability.getClientMediaPreferences = sinon.stub().resolves({id: 'fake clientMediaPreferences'});
3742
+ meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon
3743
+ .stub()
3744
+ .resolves({id: 'fake reachability'});
3745
+ meeting.webex.meetings.reachability.getClientMediaPreferences = sinon
3746
+ .stub()
3747
+ .resolves({id: 'fake clientMediaPreferences'});
3748
3748
  meeting.roap.doTurnDiscovery = sinon.stub().resolves({
3749
3749
  turnServerInfo: {
3750
3750
  url: 'turns:turn-server-url:443?transport=tcp',
@@ -3930,8 +3930,14 @@ describe('plugin-meetings', () => {
3930
3930
  const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
3931
3931
  const {sdp, seq, tieBreaker} = roapOfferMessage;
3932
3932
 
3933
- assert.calledWith(meeting.webex.meetings.reachability.getClientMediaPreferences, meeting.isMultistream, 0);
3934
- assert.calledWith(meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap);
3933
+ assert.calledWith(
3934
+ meeting.webex.meetings.reachability.getClientMediaPreferences,
3935
+ meeting.isMultistream,
3936
+ 0
3937
+ );
3938
+ assert.calledWith(
3939
+ meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap
3940
+ );
3935
3941
 
3936
3942
  assert.calledWith(locusMediaRequestStub, {
3937
3943
  method: 'PUT',
@@ -4176,7 +4182,6 @@ describe('plugin-meetings', () => {
4176
4182
  });
4177
4183
 
4178
4184
  it('addMedia() works correctly when media is enabled with streams to publish', async () => {
4179
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4180
4185
  await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
4181
4186
  await simulateRoapOffer();
4182
4187
  await simulateRoapOk();
@@ -4207,12 +4212,9 @@ describe('plugin-meetings', () => {
4207
4212
 
4208
4213
  // and that these were the only /media requests that were sent
4209
4214
  assert.calledTwice(locusMediaRequestStub);
4210
-
4211
- assert.calledOnce(handleDeviceLoggingSpy);
4212
4215
  });
4213
4216
 
4214
4217
  it('addMedia() works correctly when media is enabled with streams to publish and stream is user muted', async () => {
4215
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4216
4218
  fakeMicrophoneStream.userMuted = true;
4217
4219
 
4218
4220
  await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
@@ -4244,7 +4246,6 @@ describe('plugin-meetings', () => {
4244
4246
 
4245
4247
  // and that these were the only /media requests that were sent
4246
4248
  assert.calledTwice(locusMediaRequestStub);
4247
- assert.calledOnce(handleDeviceLoggingSpy);
4248
4249
  });
4249
4250
 
4250
4251
  it('addMedia() works correctly when media is enabled with tracks to publish and track is ended', async () => {
@@ -4316,7 +4317,6 @@ describe('plugin-meetings', () => {
4316
4317
  });
4317
4318
 
4318
4319
  it('addMedia() works correctly when media is disabled with streams to publish', async () => {
4319
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4320
4320
  await meeting.addMedia({
4321
4321
  localStreams: {microphone: fakeMicrophoneStream},
4322
4322
  audioEnabled: false,
@@ -4350,20 +4350,6 @@ describe('plugin-meetings', () => {
4350
4350
 
4351
4351
  // and that these were the only /media requests that were sent
4352
4352
  assert.calledTwice(locusMediaRequestStub);
4353
- assert.calledOnce(handleDeviceLoggingSpy);
4354
- });
4355
-
4356
- it('handleDeviceLogging not called when media is disabled', async () => {
4357
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4358
- await meeting.addMedia({
4359
- localStreams: {microphone: fakeMicrophoneStream},
4360
- audioEnabled: false,
4361
- videoEnabled: false,
4362
- });
4363
- await simulateRoapOffer();
4364
- await simulateRoapOk();
4365
-
4366
- assert.notCalled(handleDeviceLoggingSpy);
4367
4353
  });
4368
4354
 
4369
4355
  it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
@@ -4399,20 +4385,6 @@ describe('plugin-meetings', () => {
4399
4385
  assert.calledTwice(locusMediaRequestStub);
4400
4386
  });
4401
4387
 
4402
- it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
4403
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4404
- await meeting.addMedia({audioEnabled: false});
4405
- //calling handleDeviceLogging with audioEnaled as true adn videoEnabled as false
4406
- assert.calledWith(handleDeviceLoggingSpy, false, true);
4407
- });
4408
-
4409
- it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
4410
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4411
- await meeting.addMedia({videoEnabled: false});
4412
- //calling handleDeviceLogging audioEnabled as true videoEnabled as false
4413
- assert.calledWith(handleDeviceLoggingSpy, true, false);
4414
- });
4415
-
4416
4388
  it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
4417
4389
  await meeting.addMedia({videoEnabled: false});
4418
4390
  await simulateRoapOffer();
@@ -4479,13 +4451,6 @@ describe('plugin-meetings', () => {
4479
4451
  assert.calledTwice(locusMediaRequestStub);
4480
4452
  });
4481
4453
 
4482
- it('addMedia() works correctly when both shareAudio and shareVideo is disabled with no streams publish', async () => {
4483
- const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
4484
- await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false});
4485
- //calling handleDeviceLogging with audioEnabled true and videoEnabled as true
4486
- assert.calledWith(handleDeviceLoggingSpy, true, true);
4487
- });
4488
-
4489
4454
  describe('publishStreams()/unpublishStreams() calls', () => {
4490
4455
  [
4491
4456
  {mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
@@ -6332,29 +6297,74 @@ describe('plugin-meetings', () => {
6332
6297
  assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
6333
6298
  });
6334
6299
 
6335
- it('handles meetingInfoProvider webinar need registration error', async () => {
6300
+ it('handles MeetingInfoV2JoinWebinarError webinar need registration', async () => {
6336
6301
  meeting.destination = FAKE_DESTINATION;
6337
6302
  meeting.destinationType = FAKE_TYPE;
6338
6303
  meeting.attrs.meetingInfoProvider = {
6339
6304
  fetchMeetingInfo: sinon
6340
6305
  .stub()
6341
6306
  .throws(
6342
- new MeetingInfoV2WebinarRegistrationError(403021, FAKE_MEETING_INFO, 'a message')
6307
+ new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
6343
6308
  ),
6344
6309
  };
6345
6310
 
6346
6311
  await assert.isRejected(
6347
6312
  meeting.fetchMeetingInfo({sendCAevents: true}),
6348
- WebinarRegistrationError
6313
+ JoinWebinarError
6349
6314
  );
6350
6315
 
6351
6316
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6352
- assert.equal(meeting.meetingInfoFailureCode, 403021);
6353
6317
  assert.equal(
6354
6318
  meeting.meetingInfoFailureReason,
6355
6319
  MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION
6356
6320
  );
6357
6321
  });
6322
+
6323
+ it('handles MeetingInfoV2JoinWebinarError webinar need join with webcast', async () => {
6324
+ meeting.destination = FAKE_DESTINATION;
6325
+ meeting.destinationType = FAKE_TYPE;
6326
+ meeting.attrs.meetingInfoProvider = {
6327
+ fetchMeetingInfo: sinon
6328
+ .stub()
6329
+ .throws(
6330
+ new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')
6331
+ ),
6332
+ };
6333
+
6334
+ await assert.isRejected(
6335
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6336
+ JoinWebinarError
6337
+ );
6338
+
6339
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6340
+ assert.equal(
6341
+ meeting.meetingInfoFailureReason,
6342
+ MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST
6343
+ );
6344
+ });
6345
+
6346
+ it('handles MeetingInfoV2JoinWebinarError webinar need registrationId', async () => {
6347
+ meeting.destination = FAKE_DESTINATION;
6348
+ meeting.destinationType = FAKE_TYPE;
6349
+ meeting.attrs.meetingInfoProvider = {
6350
+ fetchMeetingInfo: sinon
6351
+ .stub()
6352
+ .throws(
6353
+ new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')
6354
+ ),
6355
+ };
6356
+
6357
+ await assert.isRejected(
6358
+ meeting.fetchMeetingInfo({sendCAevents: true}),
6359
+ JoinWebinarError
6360
+ );
6361
+
6362
+ assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6363
+ assert.equal(
6364
+ meeting.meetingInfoFailureReason,
6365
+ MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID
6366
+ );
6367
+ });
6358
6368
  });
6359
6369
 
6360
6370
  describe('#refreshPermissionToken', () => {
@@ -7817,7 +7827,9 @@ describe('plugin-meetings', () => {
7817
7827
  });
7818
7828
 
7819
7829
  it('should collect ice candidates', () => {
7820
- eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({candidate: {candidate: 'candidate'}});
7830
+ eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
7831
+ candidate: {candidate: 'candidate'},
7832
+ });
7821
7833
 
7822
7834
  assert.equal(meeting.iceCandidatesCount, 1);
7823
7835
  });
@@ -8123,10 +8135,10 @@ describe('plugin-meetings', () => {
8123
8135
  meeting.statsAnalyzer.stopAnalyzer = sinon.stub().resolves();
8124
8136
  meeting.reconnectionManager = {
8125
8137
  reconnect: sinon.stub().resolves(),
8126
- resetReconnectionTimer: () => {}
8138
+ resetReconnectionTimer: () => {},
8127
8139
  };
8128
8140
  meeting.currentMediaStatus = {
8129
- video: true
8141
+ video: true,
8130
8142
  };
8131
8143
 
8132
8144
  await mockFailedEvent();
@@ -8677,6 +8689,13 @@ describe('plugin-meetings', () => {
8677
8689
  {payload: test1}
8678
8690
  );
8679
8691
  assert.calledOnce(meeting.updateLLMConnection);
8692
+ assert.calledOnceWithExactly(
8693
+ Metrics.sendBehavioralMetric,
8694
+ BEHAVIORAL_METRICS.GUEST_ENTERED_LOBBY,
8695
+ {
8696
+ correlation_id: meeting.correlationId,
8697
+ }
8698
+ );
8680
8699
  done();
8681
8700
  });
8682
8701
  it('listens to the self admitted guest event', (done) => {
@@ -8698,6 +8717,13 @@ describe('plugin-meetings', () => {
8698
8717
  assert.calledOnce(meeting.updateLLMConnection);
8699
8718
  assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
8700
8719
 
8720
+ assert.calledOnceWithExactly(
8721
+ Metrics.sendBehavioralMetric,
8722
+ BEHAVIORAL_METRICS.GUEST_EXITED_LOBBY,
8723
+ {
8724
+ correlation_id: meeting.correlationId,
8725
+ }
8726
+ );
8701
8727
  done();
8702
8728
  });
8703
8729
 
@@ -9030,6 +9056,8 @@ describe('plugin-meetings', () => {
9030
9056
  });
9031
9057
 
9032
9058
  it('listens to MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED', async () => {
9059
+ meeting.webinar.updatePracticeSessionStatus = sinon.stub();
9060
+
9033
9061
  const state = {example: 'value'};
9034
9062
 
9035
9063
  await meeting.locusInfo.emitScoped(
@@ -9038,6 +9066,7 @@ describe('plugin-meetings', () => {
9038
9066
  {state}
9039
9067
  );
9040
9068
 
9069
+ assert.calledOnceWithExactly(meeting.webinar.updatePracticeSessionStatus, state);
9041
9070
  assert.calledWith(
9042
9071
  TriggerProxy.trigger,
9043
9072
  meeting,
@@ -165,21 +165,6 @@ describe('plugin-meetings', () => {
165
165
  assert(LoggerProxy.logger.log.called, 'log called');
166
166
  });
167
167
  });
168
-
169
- describe('#handleDeviceLogging', () => {
170
- it('should not log if called without devices', () => {
171
- MeetingUtil.handleDeviceLogging();
172
- assert(!LoggerProxy.logger.log.called, 'log not called');
173
- });
174
-
175
- it('should log device settings', () => {
176
- const mockDevices = [{deviceId: 'device-1'}, {deviceId: 'device-2'}];
177
-
178
- assert(MeetingUtil.handleDeviceLogging, 'is defined');
179
- MeetingUtil.handleDeviceLogging(mockDevices);
180
- assert(LoggerProxy.logger.log.called, 'log called');
181
- });
182
- });
183
168
  });
184
169
 
185
170
  describe('addSequence', () => {