@webex/plugin-meetings 3.7.0-next.8 → 3.7.0-web-workers-keepalive.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.
- package/dist/annotation/index.js +17 -0
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
- package/dist/common/errors/join-webinar-error.js.map +1 -0
- package/dist/common/errors/multistream-not-supported-error.js +53 -0
- package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +26 -5
- package/dist/constants.js.map +1 -1
- package/dist/index.js +16 -11
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +13 -2
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +30 -17
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/meeting/index.js +903 -800
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +9 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/request.js +30 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +16 -16
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +29 -17
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meetings/index.js +6 -3
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +39 -28
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +1 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/remoteMedia.js +30 -15
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +24 -0
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/roap/index.js +10 -8
- package/dist/roap/index.js.map +1 -1
- package/dist/types/annotation/index.d.ts +5 -0
- package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
- package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
- package/dist/types/constants.d.ts +20 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/locus-info/index.d.ts +2 -1
- package/dist/types/meeting/index.d.ts +19 -12
- package/dist/types/meeting/locusMediaRequest.d.ts +4 -0
- package/dist/types/meeting/request.d.ts +12 -1
- package/dist/types/meeting/request.type.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +1 -1
- package/dist/types/meeting-info/meeting-info-v2.d.ts +4 -4
- package/dist/types/meetings/index.d.ts +3 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +7 -0
- package/dist/types/metrics/constants.d.ts +1 -1
- package/dist/types/multistream/sendSlotManager.d.ts +8 -1
- package/dist/webinar/index.js +354 -3
- package/dist/webinar/index.js.map +1 -1
- package/package.json +23 -22
- package/src/annotation/index.ts +16 -0
- package/src/common/errors/join-webinar-error.ts +24 -0
- package/src/common/errors/multistream-not-supported-error.ts +30 -0
- package/src/config.ts +1 -1
- package/src/constants.ts +23 -3
- package/src/index.ts +5 -3
- package/src/locus-info/index.ts +17 -2
- package/src/locus-info/selfUtils.ts +19 -6
- package/src/meeting/index.ts +234 -80
- package/src/meeting/locusMediaRequest.ts +7 -0
- package/src/meeting/request.ts +26 -1
- package/src/meeting/request.type.ts +7 -0
- package/src/meeting/util.ts +8 -10
- package/src/meeting-info/meeting-info-v2.ts +23 -11
- package/src/meetings/index.ts +8 -2
- package/src/meetings/util.ts +2 -1
- package/src/member/index.ts +9 -0
- package/src/member/types.ts +8 -0
- package/src/member/util.ts +34 -24
- package/src/metrics/constants.ts +1 -1
- package/src/multistream/remoteMedia.ts +28 -15
- package/src/multistream/sendSlotManager.ts +31 -0
- package/src/roap/index.ts +10 -8
- package/src/webinar/index.ts +197 -3
- package/test/unit/spec/annotation/index.ts +46 -1
- package/test/unit/spec/locus-info/index.js +222 -0
- package/test/unit/spec/locus-info/selfConstant.js +7 -0
- package/test/unit/spec/locus-info/selfUtils.js +91 -1
- package/test/unit/spec/meeting/index.js +683 -103
- package/test/unit/spec/meeting/utils.js +22 -19
- package/test/unit/spec/meeting-info/meetinginfov2.js +9 -4
- package/test/unit/spec/meetings/index.js +9 -5
- package/test/unit/spec/meetings/utils.js +10 -0
- package/test/unit/spec/member/util.js +52 -11
- package/test/unit/spec/multistream/remoteMedia.ts +11 -7
- package/test/unit/spec/roap/index.ts +47 -0
- package/test/unit/spec/webinar/index.ts +457 -0
- package/dist/common/errors/webinar-registration-error.js.map +0 -1
- package/src/common/errors/webinar-registration-error.ts +0 -27
package/src/webinar/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
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 {HTTP_VERBS, MEETINGS, SELF_ROLES} from '../constants';
|
|
7
|
+
import {_ID_, HEADERS, HTTP_VERBS, MEETINGS, SELF_ROLES, SHARE_STATUS} from '../constants';
|
|
7
8
|
|
|
8
9
|
import WebinarCollection from './collection';
|
|
9
10
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
@@ -24,6 +25,7 @@ const Webinar = WebexPlugin.extend({
|
|
|
24
25
|
selfIsPanelist: 'boolean', // self is panelist
|
|
25
26
|
selfIsAttendee: 'boolean', // self is attendee
|
|
26
27
|
practiceSessionEnabled: 'boolean', // practice session enabled
|
|
28
|
+
meetingId: 'string',
|
|
27
29
|
},
|
|
28
30
|
|
|
29
31
|
/**
|
|
@@ -67,14 +69,47 @@ const Webinar = WebexPlugin.extend({
|
|
|
67
69
|
const isPromoted =
|
|
68
70
|
oldRoles.includes(SELF_ROLES.ATTENDEE) && newRoles.includes(SELF_ROLES.PANELIST);
|
|
69
71
|
const isDemoted =
|
|
70
|
-
oldRoles.includes(SELF_ROLES.PANELIST) && newRoles.includes(SELF_ROLES.ATTENDEE)
|
|
72
|
+
(oldRoles.includes(SELF_ROLES.PANELIST) && newRoles.includes(SELF_ROLES.ATTENDEE)) ||
|
|
73
|
+
(!oldRoles.includes(SELF_ROLES.ATTENDEE) && newRoles.includes(SELF_ROLES.ATTENDEE)); // for attendee just join meeting case
|
|
71
74
|
this.set('selfIsPanelist', newRoles.includes(SELF_ROLES.PANELIST));
|
|
72
75
|
this.set('selfIsAttendee', newRoles.includes(SELF_ROLES.ATTENDEE));
|
|
73
76
|
this.updateCanManageWebcast(newRoles.includes(SELF_ROLES.MODERATOR));
|
|
77
|
+
this.updateStatusByRole({isPromoted, isDemoted});
|
|
74
78
|
|
|
75
79
|
return {isPromoted, isDemoted};
|
|
76
80
|
},
|
|
77
81
|
|
|
82
|
+
/**
|
|
83
|
+
* should join practice session data channel or not
|
|
84
|
+
* @param {Object} {isPromoted: boolean, isDemoted: boolean}} Role transition states
|
|
85
|
+
* @returns {void}
|
|
86
|
+
*/
|
|
87
|
+
updateStatusByRole({isPromoted, isDemoted}) {
|
|
88
|
+
const meeting = this.webex.meetings.getMeetingByType(_ID_, this.meetingId);
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
(isDemoted && meeting?.shareStatus === SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE) ||
|
|
92
|
+
isPromoted
|
|
93
|
+
) {
|
|
94
|
+
// attendees in webinar should subscribe streaming for whiteboard sharing
|
|
95
|
+
// while panelist still need subscribe native mode so trigger force update here
|
|
96
|
+
meeting?.locusInfo?.updateMediaShares(meeting?.locusInfo?.mediaShares, true);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.practiceSessionEnabled) {
|
|
100
|
+
// may need change data channel in practice session
|
|
101
|
+
meeting?.updateLLMConnection();
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* should join practice session data channel or not
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
isJoinPracticeSessionDataChannel() {
|
|
110
|
+
return this.selfIsPanelist && this.practiceSessionEnabled;
|
|
111
|
+
},
|
|
112
|
+
|
|
78
113
|
/**
|
|
79
114
|
* start or stop practice session for webinar
|
|
80
115
|
* @param {boolean} enabled
|
|
@@ -103,6 +138,165 @@ const Webinar = WebexPlugin.extend({
|
|
|
103
138
|
updatePracticeSessionStatus(payload) {
|
|
104
139
|
this.set('practiceSessionEnabled', payload.enabled);
|
|
105
140
|
},
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* start webcast mode for webinar
|
|
144
|
+
* @param {object} meeting
|
|
145
|
+
* @param {object} layout
|
|
146
|
+
* @returns {Promise}
|
|
147
|
+
*/
|
|
148
|
+
async startWebcast(meeting, layout) {
|
|
149
|
+
if (!meeting) {
|
|
150
|
+
LoggerProxy.logger.error(
|
|
151
|
+
`Meeting:webinar#startWebcast failed --> meeting parameter : ${meeting}`
|
|
152
|
+
);
|
|
153
|
+
throw new Error('Meeting parameter does not meet expectations');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return this.request({
|
|
157
|
+
method: HTTP_VERBS.PUT,
|
|
158
|
+
uri: `${this.webcastInstanceUrl}/streaming`,
|
|
159
|
+
headers: {
|
|
160
|
+
authorization: await this.webex.credentials.getUserToken(),
|
|
161
|
+
trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
|
|
162
|
+
[HEADERS.CONTENT_TYPE]: HEADERS.CONTENT_TYPE_VALUE.APPLICATION_JSON,
|
|
163
|
+
},
|
|
164
|
+
body: {
|
|
165
|
+
action: 'start',
|
|
166
|
+
meetingInfo: {
|
|
167
|
+
locusId: meeting.locusId,
|
|
168
|
+
correlationId: meeting.correlationId,
|
|
169
|
+
},
|
|
170
|
+
layout,
|
|
171
|
+
},
|
|
172
|
+
}).catch((error) => {
|
|
173
|
+
LoggerProxy.logger.error('Meeting:webinar#startWebcast failed', error);
|
|
174
|
+
throw error;
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* stop webcast mode for webinar
|
|
180
|
+
* @returns {Promise}
|
|
181
|
+
*/
|
|
182
|
+
async stopWebcast() {
|
|
183
|
+
return this.request({
|
|
184
|
+
method: HTTP_VERBS.PUT,
|
|
185
|
+
uri: `${this.webcastInstanceUrl}/streaming`,
|
|
186
|
+
headers: {
|
|
187
|
+
authorization: await this.webex.credentials.getUserToken(),
|
|
188
|
+
trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
|
|
189
|
+
[HEADERS.CONTENT_TYPE]: HEADERS.CONTENT_TYPE_VALUE.APPLICATION_JSON,
|
|
190
|
+
},
|
|
191
|
+
body: {
|
|
192
|
+
action: 'stop',
|
|
193
|
+
},
|
|
194
|
+
}).catch((error) => {
|
|
195
|
+
LoggerProxy.logger.error('Meeting:webinar#stopWebcast failed', error);
|
|
196
|
+
throw error;
|
|
197
|
+
});
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* query webcast layout for webinar
|
|
202
|
+
* @returns {Promise}
|
|
203
|
+
*/
|
|
204
|
+
async queryWebcastLayout() {
|
|
205
|
+
return this.request({
|
|
206
|
+
method: HTTP_VERBS.GET,
|
|
207
|
+
uri: `${this.webcastInstanceUrl}/layout`,
|
|
208
|
+
headers: {
|
|
209
|
+
authorization: await this.webex.credentials.getUserToken(),
|
|
210
|
+
trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
|
|
211
|
+
},
|
|
212
|
+
}).catch((error) => {
|
|
213
|
+
LoggerProxy.logger.error('Meeting:webinar#queryWebcastLayout failed', error);
|
|
214
|
+
throw error;
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* update webcast layout for webinar
|
|
220
|
+
* @param {object} layout
|
|
221
|
+
* @returns {Promise}
|
|
222
|
+
*/
|
|
223
|
+
async updateWebcastLayout(layout) {
|
|
224
|
+
return this.request({
|
|
225
|
+
method: HTTP_VERBS.PUT,
|
|
226
|
+
uri: `${this.webcastInstanceUrl}/layout`,
|
|
227
|
+
headers: {
|
|
228
|
+
authorization: await this.webex.credentials.getUserToken(),
|
|
229
|
+
trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
|
|
230
|
+
[HEADERS.CONTENT_TYPE]: HEADERS.CONTENT_TYPE_VALUE.APPLICATION_JSON,
|
|
231
|
+
},
|
|
232
|
+
body: {
|
|
233
|
+
videoLayout: layout.videoLayout,
|
|
234
|
+
contentLayout: layout.contentLayout,
|
|
235
|
+
syncStageLayout: layout.syncStageLayout,
|
|
236
|
+
syncStageInMeeting: layout.syncStageInMeeting,
|
|
237
|
+
},
|
|
238
|
+
}).catch((error) => {
|
|
239
|
+
LoggerProxy.logger.error('Meeting:webinar#updateWebcastLayout failed', error);
|
|
240
|
+
throw error;
|
|
241
|
+
});
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* view all webcast attendees
|
|
246
|
+
* @param {string} queryString
|
|
247
|
+
* @returns {Promise}
|
|
248
|
+
*/
|
|
249
|
+
async viewAllWebcastAttendees() {
|
|
250
|
+
return this.request({
|
|
251
|
+
method: HTTP_VERBS.GET,
|
|
252
|
+
uri: `${this.webcastInstanceUrl}/attendees`,
|
|
253
|
+
headers: {
|
|
254
|
+
authorization: await this.webex.credentials.getUserToken(),
|
|
255
|
+
trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
|
|
256
|
+
},
|
|
257
|
+
}).catch((error) => {
|
|
258
|
+
LoggerProxy.logger.error('Meeting:webinar#viewAllWebcastAttendees failed', error);
|
|
259
|
+
throw error;
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* search webcast attendees by query string
|
|
265
|
+
* @param {string} queryString
|
|
266
|
+
* @returns {Promise}
|
|
267
|
+
*/
|
|
268
|
+
async searchWebcastAttendees(queryString = '') {
|
|
269
|
+
return this.request({
|
|
270
|
+
method: HTTP_VERBS.GET,
|
|
271
|
+
uri: `${this.webcastInstanceUrl}/attendees?keyword=${encodeURIComponent(queryString)}`,
|
|
272
|
+
headers: {
|
|
273
|
+
authorization: await this.webex.credentials.getUserToken(),
|
|
274
|
+
trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
|
|
275
|
+
},
|
|
276
|
+
}).catch((error) => {
|
|
277
|
+
LoggerProxy.logger.error('Meeting:webinar#searchWebcastAttendees failed', error);
|
|
278
|
+
throw error;
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* expel webcast attendee by participantId
|
|
284
|
+
* @param {string} participantId
|
|
285
|
+
* @returns {Promise}
|
|
286
|
+
*/
|
|
287
|
+
async expelWebcastAttendee(participantId) {
|
|
288
|
+
return this.request({
|
|
289
|
+
method: HTTP_VERBS.DELETE,
|
|
290
|
+
uri: `${this.webcastInstanceUrl}/attendees/${participantId}`,
|
|
291
|
+
headers: {
|
|
292
|
+
authorization: await this.webex.credentials.getUserToken(),
|
|
293
|
+
trackingId: `${config.trackingIdPrefix}_${uuid.v4().toString()}`,
|
|
294
|
+
},
|
|
295
|
+
}).catch((error) => {
|
|
296
|
+
LoggerProxy.logger.error('Meeting:webinar#expelWebcastAttendee failed', error);
|
|
297
|
+
throw error;
|
|
298
|
+
});
|
|
299
|
+
},
|
|
106
300
|
});
|
|
107
301
|
|
|
108
302
|
export default Webinar;
|
|
@@ -413,6 +413,51 @@ describe('live-annotation', () => {
|
|
|
413
413
|
});
|
|
414
414
|
});
|
|
415
415
|
});
|
|
416
|
-
});
|
|
417
416
|
|
|
417
|
+
describe('#deregisterEvents', () => {
|
|
418
|
+
let llmOn;
|
|
419
|
+
let llmOff;
|
|
420
|
+
let mercuryOn;
|
|
421
|
+
let mercuryOff;
|
|
422
|
+
|
|
423
|
+
beforeEach(() => {
|
|
424
|
+
llmOn = sinon.spy(webex.internal.llm, 'on');
|
|
425
|
+
llmOff = sinon.spy(webex.internal.llm, 'off');
|
|
426
|
+
mercuryOn = sinon.spy(webex.internal.mercury, 'on');
|
|
427
|
+
mercuryOff = sinon.spy(webex.internal.mercury, 'off');
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it('cleans up events', () => {
|
|
431
|
+
annotationService.locusUrlUpdate(locusUrl);
|
|
432
|
+
assert.calledWith(
|
|
433
|
+
mercuryOn,
|
|
434
|
+
'event:locus.approval_request',
|
|
435
|
+
annotationService.eventCommandProcessor,
|
|
436
|
+
annotationService
|
|
437
|
+
);
|
|
438
|
+
assert.calledWith(
|
|
439
|
+
llmOn,
|
|
440
|
+
'event:relay.event',
|
|
441
|
+
annotationService.eventDataProcessor,
|
|
442
|
+
annotationService
|
|
443
|
+
);
|
|
444
|
+
assert.match(annotationService.hasSubscribedToEvents, true);
|
|
445
|
+
|
|
446
|
+
annotationService.deregisterEvents();
|
|
447
|
+
assert.calledWith(llmOff, 'event:relay.event', annotationService.eventDataProcessor);
|
|
448
|
+
assert.calledWith(
|
|
449
|
+
mercuryOff,
|
|
450
|
+
'event:locus.approval_request',
|
|
451
|
+
annotationService.eventCommandProcessor
|
|
452
|
+
);
|
|
453
|
+
assert.match(annotationService.hasSubscribedToEvents, false);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('does not call llm off if events have not been registered', () => {
|
|
457
|
+
annotationService.deregisterEvents();
|
|
458
|
+
assert.notCalled(llmOff);
|
|
459
|
+
assert.notCalled(mercuryOff);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
});
|
|
418
463
|
});
|
|
@@ -9,6 +9,7 @@ import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
|
|
9
9
|
import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
|
|
10
10
|
import InfoUtils from '@webex/plugin-meetings/src/locus-info/infoUtils';
|
|
11
11
|
import EmbeddedAppsUtils from '@webex/plugin-meetings/src/locus-info/embeddedAppsUtils';
|
|
12
|
+
import MediaSharesUtils from '@webex/plugin-meetings/src/locus-info//mediaSharesUtils';
|
|
12
13
|
import LocusDeltaParser from '@webex/plugin-meetings/src/locus-info/parser';
|
|
13
14
|
import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
14
15
|
|
|
@@ -793,6 +794,75 @@ describe('plugin-meetings', () => {
|
|
|
793
794
|
});
|
|
794
795
|
|
|
795
796
|
describe('#updateSelf', () => {
|
|
797
|
+
it('should trigger SELF_MEETING_BRB_CHANGED when brb state changed', () => {
|
|
798
|
+
locusInfo.self = undefined;
|
|
799
|
+
|
|
800
|
+
const assertBrb = (enabled) => {
|
|
801
|
+
const selfWithBrbChanged = cloneDeep(self);
|
|
802
|
+
selfWithBrbChanged.controls.brb = enabled;
|
|
803
|
+
|
|
804
|
+
locusInfo.emitScoped = sinon.stub();
|
|
805
|
+
locusInfo.updateSelf(selfWithBrbChanged, []);
|
|
806
|
+
|
|
807
|
+
assert.calledWith(
|
|
808
|
+
locusInfo.emitScoped,
|
|
809
|
+
{file: 'locus-info', function: 'updateSelf'},
|
|
810
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
|
|
811
|
+
{brb: enabled}
|
|
812
|
+
);
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
assertBrb(true);
|
|
816
|
+
assertBrb(false);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
it('should not trigger SELF_MEETING_BRB_CHANGED when brb state did not change', () => {
|
|
820
|
+
const assertBrbUnchanged = (value) => {
|
|
821
|
+
locusInfo.self = undefined;
|
|
822
|
+
|
|
823
|
+
const selfWithBrbChanged = cloneDeep(self);
|
|
824
|
+
selfWithBrbChanged.controls.brb = value;
|
|
825
|
+
locusInfo.self = selfWithBrbChanged;
|
|
826
|
+
|
|
827
|
+
locusInfo.emitScoped = sinon.stub();
|
|
828
|
+
|
|
829
|
+
const newSelf = cloneDeep(self);
|
|
830
|
+
newSelf.controls.brb = value;
|
|
831
|
+
|
|
832
|
+
locusInfo.updateSelf(newSelf, []);
|
|
833
|
+
|
|
834
|
+
assert.neverCalledWith(
|
|
835
|
+
locusInfo.emitScoped,
|
|
836
|
+
{file: 'locus-info', function: 'updateSelf'},
|
|
837
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
|
|
838
|
+
{brb: value}
|
|
839
|
+
);
|
|
840
|
+
};
|
|
841
|
+
|
|
842
|
+
assertBrbUnchanged(true);
|
|
843
|
+
assertBrbUnchanged(false);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it('should not trigger SELF_MEETING_BRB_CHANGED when brb state is undefined', () => {
|
|
847
|
+
const selfWithBrbChanged = cloneDeep(self);
|
|
848
|
+
selfWithBrbChanged.controls.brb = false;
|
|
849
|
+
locusInfo.self = selfWithBrbChanged;
|
|
850
|
+
|
|
851
|
+
locusInfo.emitScoped = sinon.stub();
|
|
852
|
+
|
|
853
|
+
const newSelf = cloneDeep(self);
|
|
854
|
+
newSelf.controls.brb = undefined;
|
|
855
|
+
|
|
856
|
+
locusInfo.updateSelf(newSelf, []);
|
|
857
|
+
|
|
858
|
+
assert.neverCalledWith(
|
|
859
|
+
locusInfo.emitScoped,
|
|
860
|
+
{file: 'locus-info', function: 'updateSelf'},
|
|
861
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
|
|
862
|
+
{brb: undefined}
|
|
863
|
+
);
|
|
864
|
+
});
|
|
865
|
+
|
|
796
866
|
it('should trigger CONTROLS_MEETING_LAYOUT_UPDATED when the meeting layout controls change', () => {
|
|
797
867
|
const layoutType = 'EXAMPLE TYPE';
|
|
798
868
|
|
|
@@ -1388,6 +1458,30 @@ describe('plugin-meetings', () => {
|
|
|
1388
1458
|
}
|
|
1389
1459
|
);
|
|
1390
1460
|
});
|
|
1461
|
+
|
|
1462
|
+
it('should not trigger any events if controls is undefined', () => {
|
|
1463
|
+
locusInfo.self = self;
|
|
1464
|
+
locusInfo.emitScoped = sinon.stub();
|
|
1465
|
+
const newSelf = cloneDeep(self);
|
|
1466
|
+
newSelf.controls = undefined;
|
|
1467
|
+
|
|
1468
|
+
locusInfo.updateSelf(newSelf, []);
|
|
1469
|
+
|
|
1470
|
+
const eventsSet = new Set([
|
|
1471
|
+
LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED,
|
|
1472
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED,
|
|
1473
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
|
|
1474
|
+
LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED,
|
|
1475
|
+
LOCUSINFO.EVENTS.LOCAL_UNMUTE_REQUIRED,
|
|
1476
|
+
LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
|
|
1477
|
+
]);
|
|
1478
|
+
|
|
1479
|
+
// check all events that contain logic on controls existence
|
|
1480
|
+
locusInfo.emitScoped.getCalls().forEach((call) => {
|
|
1481
|
+
const eventName = call.args[1];
|
|
1482
|
+
assert.isFalse(eventsSet.has(eventName));
|
|
1483
|
+
});
|
|
1484
|
+
});
|
|
1391
1485
|
});
|
|
1392
1486
|
|
|
1393
1487
|
describe('#updateMeetingInfo', () => {
|
|
@@ -1637,6 +1731,134 @@ describe('plugin-meetings', () => {
|
|
|
1637
1731
|
});
|
|
1638
1732
|
});
|
|
1639
1733
|
|
|
1734
|
+
describe('#updateMediaShares', () => {
|
|
1735
|
+
let getMediaSharesSpy;
|
|
1736
|
+
|
|
1737
|
+
beforeEach(() => {
|
|
1738
|
+
// Spy on MediaSharesUtils.getMediaShares
|
|
1739
|
+
getMediaSharesSpy = sinon.stub(MediaSharesUtils, 'getMediaShares');
|
|
1740
|
+
|
|
1741
|
+
// Stub the emitScoped method to monitor its calls
|
|
1742
|
+
sinon.stub(locusInfo, 'emitScoped');
|
|
1743
|
+
});
|
|
1744
|
+
|
|
1745
|
+
afterEach(() => {
|
|
1746
|
+
getMediaSharesSpy.restore();
|
|
1747
|
+
locusInfo.emitScoped.restore();
|
|
1748
|
+
});
|
|
1749
|
+
|
|
1750
|
+
it('should update media shares and emit LOCUS_INFO_UPDATE_MEDIA_SHARES when mediaShares change', () => {
|
|
1751
|
+
const initialMediaShares = { audio: true, video: false };
|
|
1752
|
+
const newMediaShares = { audio: false, video: true };
|
|
1753
|
+
|
|
1754
|
+
locusInfo.mediaShares = initialMediaShares;
|
|
1755
|
+
locusInfo.parsedLocus = { mediaShares: null };
|
|
1756
|
+
|
|
1757
|
+
const parsedMediaShares = {
|
|
1758
|
+
current: newMediaShares,
|
|
1759
|
+
previous: initialMediaShares,
|
|
1760
|
+
};
|
|
1761
|
+
|
|
1762
|
+
// Stub MediaSharesUtils.getMediaShares to return the expected parsedMediaShares
|
|
1763
|
+
getMediaSharesSpy.returns(parsedMediaShares);
|
|
1764
|
+
|
|
1765
|
+
// Call the function
|
|
1766
|
+
locusInfo.updateMediaShares(newMediaShares);
|
|
1767
|
+
|
|
1768
|
+
// Assert that MediaSharesUtils.getMediaShares was called with correct arguments
|
|
1769
|
+
assert.calledWith(getMediaSharesSpy, initialMediaShares, newMediaShares);
|
|
1770
|
+
|
|
1771
|
+
// Assert that updateMeeting was called with the parsed current media shares
|
|
1772
|
+
assert.deepEqual(locusInfo.parsedLocus.mediaShares, newMediaShares);
|
|
1773
|
+
assert.deepEqual(locusInfo.mediaShares, newMediaShares);
|
|
1774
|
+
|
|
1775
|
+
// Assert that emitScoped was called with the correct event
|
|
1776
|
+
assert.calledWith(
|
|
1777
|
+
locusInfo.emitScoped,
|
|
1778
|
+
{
|
|
1779
|
+
file: 'locus-info',
|
|
1780
|
+
function: 'updateMediaShares',
|
|
1781
|
+
},
|
|
1782
|
+
EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
|
|
1783
|
+
{
|
|
1784
|
+
current: newMediaShares,
|
|
1785
|
+
previous: initialMediaShares,
|
|
1786
|
+
forceUpdate: false,
|
|
1787
|
+
}
|
|
1788
|
+
);
|
|
1789
|
+
});
|
|
1790
|
+
|
|
1791
|
+
it('should force update media shares and emit LOCUS_INFO_UPDATE_MEDIA_SHARES even if shares are the same', () => {
|
|
1792
|
+
const initialMediaShares = { audio: true, video: false };
|
|
1793
|
+
locusInfo.mediaShares = initialMediaShares;
|
|
1794
|
+
locusInfo.parsedLocus = { mediaShares: null };
|
|
1795
|
+
|
|
1796
|
+
const parsedMediaShares = {
|
|
1797
|
+
current: initialMediaShares,
|
|
1798
|
+
previous: initialMediaShares,
|
|
1799
|
+
};
|
|
1800
|
+
|
|
1801
|
+
getMediaSharesSpy.returns(parsedMediaShares);
|
|
1802
|
+
|
|
1803
|
+
// Call the function with forceUpdate = true
|
|
1804
|
+
locusInfo.updateMediaShares(initialMediaShares, true);
|
|
1805
|
+
|
|
1806
|
+
// Assert that MediaSharesUtils.getMediaShares was called
|
|
1807
|
+
assert.calledWith(getMediaSharesSpy, initialMediaShares, initialMediaShares);
|
|
1808
|
+
|
|
1809
|
+
// Assert that emitScoped was called with the correct event
|
|
1810
|
+
assert.calledWith(
|
|
1811
|
+
locusInfo.emitScoped,
|
|
1812
|
+
{
|
|
1813
|
+
file: 'locus-info',
|
|
1814
|
+
function: 'updateMediaShares',
|
|
1815
|
+
},
|
|
1816
|
+
EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES,
|
|
1817
|
+
{
|
|
1818
|
+
current: initialMediaShares,
|
|
1819
|
+
previous: initialMediaShares,
|
|
1820
|
+
forceUpdate: true,
|
|
1821
|
+
}
|
|
1822
|
+
);
|
|
1823
|
+
});
|
|
1824
|
+
|
|
1825
|
+
it('should not emit LOCUS_INFO_UPDATE_MEDIA_SHARES if mediaShares do not change and forceUpdate is false', () => {
|
|
1826
|
+
const initialMediaShares = { audio: true, video: false };
|
|
1827
|
+
locusInfo.mediaShares = initialMediaShares;
|
|
1828
|
+
|
|
1829
|
+
// Call the function with the same mediaShares and forceUpdate = false
|
|
1830
|
+
locusInfo.updateMediaShares(initialMediaShares);
|
|
1831
|
+
|
|
1832
|
+
// Assert that MediaSharesUtils.getMediaShares was not called
|
|
1833
|
+
assert.notCalled(getMediaSharesSpy);
|
|
1834
|
+
|
|
1835
|
+
// Assert that emitScoped was not called
|
|
1836
|
+
assert.notCalled(locusInfo.emitScoped);
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
it('should update internal state correctly when mediaShares are updated', () => {
|
|
1840
|
+
const initialMediaShares = { audio: true, video: false };
|
|
1841
|
+
const newMediaShares = { audio: false, video: true };
|
|
1842
|
+
|
|
1843
|
+
locusInfo.mediaShares = initialMediaShares;
|
|
1844
|
+
locusInfo.parsedLocus = { mediaShares: null };
|
|
1845
|
+
|
|
1846
|
+
const parsedMediaShares = {
|
|
1847
|
+
current: newMediaShares,
|
|
1848
|
+
previous: initialMediaShares,
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
getMediaSharesSpy.returns(parsedMediaShares);
|
|
1852
|
+
|
|
1853
|
+
// Call the function
|
|
1854
|
+
locusInfo.updateMediaShares(newMediaShares);
|
|
1855
|
+
|
|
1856
|
+
// Assert that the internal state was updated correctly
|
|
1857
|
+
assert.deepEqual(locusInfo.parsedLocus.mediaShares, newMediaShares);
|
|
1858
|
+
assert.deepEqual(locusInfo.mediaShares, newMediaShares);
|
|
1859
|
+
});
|
|
1860
|
+
});
|
|
1861
|
+
|
|
1640
1862
|
describe('#updateEmbeddedApps()', () => {
|
|
1641
1863
|
const newEmbeddedApps = [
|
|
1642
1864
|
{
|
|
@@ -304,6 +304,13 @@ export const selfWithInactivity = {
|
|
|
304
304
|
localRecord: {
|
|
305
305
|
recording: false,
|
|
306
306
|
},
|
|
307
|
+
brb: {
|
|
308
|
+
enabled: true,
|
|
309
|
+
meta: {
|
|
310
|
+
lastModified: '2024-10-24T14:05:58.526Z',
|
|
311
|
+
modifiedBy: '70978427-8238-4ffc-9227-8baf4b80b831',
|
|
312
|
+
},
|
|
313
|
+
},
|
|
307
314
|
layouts: [
|
|
308
315
|
{
|
|
309
316
|
type: 'activePresence',
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {assert} from '@webex/test-helper-chai';
|
|
2
2
|
import Sinon from 'sinon';
|
|
3
|
-
import {cloneDeep} from 'lodash';
|
|
3
|
+
import {cloneDeep, defaultsDeep} from 'lodash';
|
|
4
4
|
import SelfUtils from '@webex/plugin-meetings/src/locus-info/selfUtils';
|
|
5
5
|
|
|
6
6
|
import {self} from './selfConstant';
|
|
7
|
+
import {_IDLE_, _WAIT_} from '@webex/plugin-meetings/src/constants';
|
|
7
8
|
|
|
8
9
|
describe('plugin-meetings', () => {
|
|
9
10
|
describe('selfUtils', () => {
|
|
@@ -60,6 +61,14 @@ describe('plugin-meetings', () => {
|
|
|
60
61
|
assert.calledWith(spy, self);
|
|
61
62
|
assert.deepEqual(parsedSelf.layout, self.controls.layouts[0].type);
|
|
62
63
|
});
|
|
64
|
+
|
|
65
|
+
it('calls getBrb and returns the resulting brb value', () => {
|
|
66
|
+
const spy = Sinon.spy(SelfUtils, 'getBrb');
|
|
67
|
+
const parsedSelf = SelfUtils.parse(self);
|
|
68
|
+
|
|
69
|
+
assert.calledWith(spy, self);
|
|
70
|
+
assert.deepEqual(parsedSelf.brb, self.controls.brb);
|
|
71
|
+
});
|
|
63
72
|
});
|
|
64
73
|
|
|
65
74
|
describe('getLayout', () => {
|
|
@@ -170,6 +179,37 @@ describe('plugin-meetings', () => {
|
|
|
170
179
|
});
|
|
171
180
|
});
|
|
172
181
|
|
|
182
|
+
describe('brbChanged', () => {
|
|
183
|
+
it('should return true if brb have changed', () => {
|
|
184
|
+
const current = {
|
|
185
|
+
brb: {enabled: true},
|
|
186
|
+
};
|
|
187
|
+
const previous = {
|
|
188
|
+
brb: {enabled: false},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
assert.isTrue(SelfUtils.brbChanged(previous, current));
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should return false if brb have not changed', () => {
|
|
195
|
+
const current = {
|
|
196
|
+
brb: {enabled: true},
|
|
197
|
+
};
|
|
198
|
+
const previous = {
|
|
199
|
+
brb: {enabled: true},
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
assert.isFalse(SelfUtils.brbChanged(previous, current));
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('should return false if brb in current is undefined', () => {
|
|
206
|
+
const current = {};
|
|
207
|
+
const previous = {brb: {enabled: true}};
|
|
208
|
+
|
|
209
|
+
assert.isFalse(SelfUtils.brbChanged(previous, current));
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
173
213
|
describe('canNotViewTheParticipantList', () => {
|
|
174
214
|
it('should return the correct value', () => {
|
|
175
215
|
assert.equal(
|
|
@@ -299,6 +339,56 @@ describe('plugin-meetings', () => {
|
|
|
299
339
|
assert.equal(updates.localAudioUnmuteRequestedByServer, false);
|
|
300
340
|
});
|
|
301
341
|
});
|
|
342
|
+
|
|
343
|
+
describe('updates.isUserUnadmitted', () => {
|
|
344
|
+
const testIsUserUnadmitted = (previousObjectDelta, currentObjectDelta, expected) => function () {
|
|
345
|
+
const previous =
|
|
346
|
+
previousObjectDelta === undefined ? undefined : defaultsDeep(previousObjectDelta, self);
|
|
347
|
+
const current = defaultsDeep(currentObjectDelta, self);
|
|
348
|
+
|
|
349
|
+
const {updates} = SelfUtils.getSelves(previous, current, self.devices[0].url);
|
|
350
|
+
|
|
351
|
+
assert.equal(updates.isUserUnadmitted, expected);
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
it(
|
|
355
|
+
'should return true when previous is undefined and current is in lobby',
|
|
356
|
+
testIsUserUnadmitted(
|
|
357
|
+
undefined,
|
|
358
|
+
{devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
|
|
359
|
+
true
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
it(
|
|
364
|
+
'should return false when previous is undefined and user is not in meeting',
|
|
365
|
+
testIsUserUnadmitted(undefined, {devices: [], state: _IDLE_}, false)
|
|
366
|
+
);
|
|
367
|
+
|
|
368
|
+
it(
|
|
369
|
+
'should return false when previous is undefined and current is in meeting',
|
|
370
|
+
testIsUserUnadmitted(undefined, {}, false)
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
it(
|
|
374
|
+
'should return false when previous is in lobby and current is in lobby',
|
|
375
|
+
testIsUserUnadmitted(
|
|
376
|
+
{devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
|
|
377
|
+
{devices: [{intent: {type: _WAIT_}}], state: _IDLE_},
|
|
378
|
+
false
|
|
379
|
+
)
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
it(
|
|
383
|
+
'should return false when previous is in lobby and current is in meeting',
|
|
384
|
+
testIsUserUnadmitted({devices: [{intent: {type: _WAIT_}}], state: _IDLE_}, {}, false)
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
it(
|
|
388
|
+
'should return true when previous is in meeting and current is in lobby',
|
|
389
|
+
testIsUserUnadmitted({}, {devices: [{intent: {type: _WAIT_}}], state: _IDLE_}, true)
|
|
390
|
+
);
|
|
391
|
+
});
|
|
302
392
|
});
|
|
303
393
|
|
|
304
394
|
describe('isSharingBlocked', () => {
|