@webex/plugin-meetings 3.12.0-next.4 → 3.12.0-next.40
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +6 -2
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +23 -21
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +554 -350
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +274 -85
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js +16 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/meeting/index.js +710 -499
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +1 -0
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +174 -77
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +49 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +53 -15
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +46 -6
- package/dist/types/locus-info/types.d.ts +17 -1
- package/dist/types/meeting/index.d.ts +64 -1
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +1 -0
- package/dist/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +2 -1
- package/src/constants.ts +1 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +26 -19
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +278 -160
- package/src/hashTree/utils.ts +17 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/locus-info/index.ts +274 -93
- package/src/locus-info/types.ts +19 -1
- package/src/meeting/index.ts +206 -22
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +77 -43
- package/src/meetings/util.ts +56 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/webinar/index.ts +75 -1
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +7 -3
- package/test/unit/spec/controls-options-manager/index.js +114 -6
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +996 -51
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/locus-info/index.js +397 -81
- package/test/unit/spec/meeting/index.js +271 -44
- package/test/unit/spec/meeting/utils.js +4 -0
- package/test/unit/spec/meetings/index.js +195 -13
- package/test/unit/spec/meetings/utils.js +137 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/webinar/index.ts +60 -0
package/src/meetings/util.ts
CHANGED
|
@@ -18,6 +18,7 @@ import Trigger from '../common/events/trigger-proxy';
|
|
|
18
18
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
19
19
|
import Metrics from '../metrics';
|
|
20
20
|
import {MEETING_KEY} from './meetings.types';
|
|
21
|
+
import {EndMeetingReason, LocusFullState} from '../locus-info/types';
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Meetings Media Codec Missing Event
|
|
@@ -266,6 +267,35 @@ MeetingsUtil.getThisDevice = (newLocus: any, deviceUrl: string) => {
|
|
|
266
267
|
return null;
|
|
267
268
|
};
|
|
268
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Checks if the fullState indicates the meeting has fully ended (not just a breakout move).
|
|
272
|
+
* @param {Object} fullState locus fullState data
|
|
273
|
+
* @returns {boolean}
|
|
274
|
+
*/
|
|
275
|
+
MeetingsUtil.isWholeMeetingEnded = (fullState: LocusFullState): boolean => {
|
|
276
|
+
return (
|
|
277
|
+
fullState.state === LOCUS.STATE.INACTIVE &&
|
|
278
|
+
fullState.endMeetingReason !== EndMeetingReason.breakoutEnded
|
|
279
|
+
);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Checks if the self state in a locus indicates a breakout move or breakout end.
|
|
284
|
+
* Returns true when:
|
|
285
|
+
* - self state is LEFT with reason MOVED (regular breakout move), OR
|
|
286
|
+
* - fullState is INACTIVE with endMeetingReason BREAKOUT_ENDED (breakout session ended)
|
|
287
|
+
* @param {Object} locus locus data
|
|
288
|
+
* @returns {boolean}
|
|
289
|
+
*/
|
|
290
|
+
MeetingsUtil.isSelfMovedOrBreakoutEnded = (locus: any): boolean => {
|
|
291
|
+
const isSelfLeftMoved = locus?.self?.state === _LEFT_ && locus?.self?.reason === _MOVED_;
|
|
292
|
+
const isBreakoutEnded =
|
|
293
|
+
locus?.fullState?.state === LOCUS.STATE.INACTIVE &&
|
|
294
|
+
locus?.fullState?.endMeetingReason === EndMeetingReason.breakoutEnded;
|
|
295
|
+
|
|
296
|
+
return isSelfLeftMoved || isBreakoutEnded;
|
|
297
|
+
};
|
|
298
|
+
|
|
269
299
|
/**
|
|
270
300
|
* get self device joined status from locus data
|
|
271
301
|
* @param {Object} meeting current meeting data
|
|
@@ -294,7 +324,10 @@ MeetingsUtil.joinedOnThisDevice = (meeting: any, newLocus: any, deviceUrl: strin
|
|
|
294
324
|
* @private
|
|
295
325
|
*/
|
|
296
326
|
MeetingsUtil.isBreakoutLocusDTO = (newLocus: any) => {
|
|
297
|
-
return
|
|
327
|
+
return (
|
|
328
|
+
newLocus?.controls?.breakout?.sessionType === BREAKOUTS.SESSION_TYPES.BREAKOUT ||
|
|
329
|
+
!!newLocus?.info?.isBreakout
|
|
330
|
+
);
|
|
298
331
|
};
|
|
299
332
|
|
|
300
333
|
/**
|
|
@@ -310,4 +343,26 @@ MeetingsUtil.isValidBreakoutLocus = (locus: any) => {
|
|
|
310
343
|
|
|
311
344
|
return isLocusAsBreakout && !inActiveStatus && selfJoined;
|
|
312
345
|
};
|
|
346
|
+
/**
|
|
347
|
+
* check if the breakout locus is associated with the main locus by comparing the breakout control url or the replaces info in self device
|
|
348
|
+
* @param {Object} mainLocus main locus data
|
|
349
|
+
* @param {Object} breakoutLocus breakout locus data
|
|
350
|
+
* @returns {boolean}
|
|
351
|
+
* @private
|
|
352
|
+
*/
|
|
353
|
+
MeetingsUtil.isMainAssociatedWithBreakout = (mainLocus: any, breakoutLocus: any) => {
|
|
354
|
+
if (
|
|
355
|
+
mainLocus.controls?.breakout?.url &&
|
|
356
|
+
mainLocus.controls?.breakout?.url === breakoutLocus.controls?.breakout?.url
|
|
357
|
+
) {
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
const deviceUrl = breakoutLocus?.self?.deviceUrl;
|
|
361
|
+
const replaceInfo = MeetingsUtil.getThisDevice(breakoutLocus, deviceUrl)?.replaces?.[0];
|
|
362
|
+
if (replaceInfo?.locusUrl && replaceInfo.locusUrl === mainLocus.url) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return false;
|
|
367
|
+
};
|
|
313
368
|
export default MeetingsUtil;
|
package/src/member/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export default class Member {
|
|
|
27
27
|
isModerator: any;
|
|
28
28
|
isModeratorAssignmentProhibited: any;
|
|
29
29
|
isPresenterAssignmentProhibited: any;
|
|
30
|
+
isAttendeeAssignmentProhibited: any;
|
|
30
31
|
isMutable: any;
|
|
31
32
|
isNotAdmitted: any;
|
|
32
33
|
isRecording: any;
|
|
@@ -292,6 +293,14 @@ export default class Member {
|
|
|
292
293
|
*/
|
|
293
294
|
this.isPresenterAssignmentProhibited = null;
|
|
294
295
|
|
|
296
|
+
/**
|
|
297
|
+
* @instance
|
|
298
|
+
* @type {Boolean}
|
|
299
|
+
* @public
|
|
300
|
+
* @memberof Member
|
|
301
|
+
*/
|
|
302
|
+
this.isAttendeeAssignmentProhibited = null;
|
|
303
|
+
|
|
295
304
|
/**
|
|
296
305
|
* @instance
|
|
297
306
|
* @type {Boolean}
|
|
@@ -369,6 +378,7 @@ export default class Member {
|
|
|
369
378
|
MemberUtil.isModeratorAssignmentProhibited(participant);
|
|
370
379
|
this.isPresenterAssignmentProhibited =
|
|
371
380
|
MemberUtil.isPresenterAssignmentProhibited(participant);
|
|
381
|
+
this.isAttendeeAssignmentProhibited = MemberUtil.isAttendeeAssignmentProhibited(participant);
|
|
372
382
|
this.canApproveAIEnablement = MemberUtil.canApproveAIEnablement(participant);
|
|
373
383
|
this.processStatus(participant);
|
|
374
384
|
this.processRoles(participant);
|
package/src/member/types.ts
CHANGED
|
@@ -103,6 +103,7 @@ export interface Participant {
|
|
|
103
103
|
moderator: boolean; // Locus docs say this is deprecated and role control should be used instead
|
|
104
104
|
moderatorAssignmentNotAllowed: boolean;
|
|
105
105
|
presenterAssignmentNotAllowed: boolean;
|
|
106
|
+
attendeeAssignmentNotAllowed?: boolean;
|
|
106
107
|
person: ParticipantPerson;
|
|
107
108
|
resourceGuest: boolean;
|
|
108
109
|
state: string; // probably one of MEETING_STATE.STATES
|
package/src/member/util.ts
CHANGED
|
@@ -140,6 +140,9 @@ const MemberUtil = {
|
|
|
140
140
|
isPresenterAssignmentProhibited: (participant: Participant) =>
|
|
141
141
|
participant && participant.presenterAssignmentNotAllowed,
|
|
142
142
|
|
|
143
|
+
isAttendeeAssignmentProhibited: (participant: Participant) =>
|
|
144
|
+
!!(participant && participant.attendeeAssignmentNotAllowed),
|
|
145
|
+
|
|
143
146
|
/**
|
|
144
147
|
* checks to see if the participant id is the same as the passed id
|
|
145
148
|
* there are multiple ids that can be used
|
package/src/webinar/index.ts
CHANGED
|
@@ -154,12 +154,63 @@ const Webinar = WebexPlugin.extend({
|
|
|
154
154
|
);
|
|
155
155
|
},
|
|
156
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Ensures practice-session token exists before registering the practice LLM channel.
|
|
159
|
+
* @param {object} meeting
|
|
160
|
+
* @returns {Promise<string|undefined>}
|
|
161
|
+
*/
|
|
162
|
+
async ensurePracticeSessionDatachannelToken(meeting) {
|
|
163
|
+
// @ts-ignore
|
|
164
|
+
const isDataChannelTokenEnabled = await this.webex.internal.llm.isDataChannelTokenEnabled();
|
|
165
|
+
|
|
166
|
+
if (!isDataChannelTokenEnabled) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// @ts-ignore
|
|
171
|
+
const cachedToken = this.webex.internal.llm.getDatachannelToken(
|
|
172
|
+
DataChannelTokenType.PracticeSession
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
if (cachedToken) {
|
|
176
|
+
return cachedToken;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const refreshResponse = await meeting.refreshDataChannelToken();
|
|
181
|
+
const {datachannelToken, dataChannelTokenType} = refreshResponse?.body ?? {};
|
|
182
|
+
|
|
183
|
+
if (!datachannelToken) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// @ts-ignore
|
|
188
|
+
this.webex.internal.llm.setDatachannelToken(
|
|
189
|
+
datachannelToken,
|
|
190
|
+
dataChannelTokenType || DataChannelTokenType.PracticeSession
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return datachannelToken;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
LoggerProxy.logger.warn(
|
|
196
|
+
`Webinar:index#ensurePracticeSessionDatachannelToken --> failed to proactively refresh practice-session token: ${
|
|
197
|
+
error?.message || String(error)
|
|
198
|
+
}`
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
157
205
|
/**
|
|
158
206
|
* Connects to low latency mercury and reconnects if the address has changed
|
|
159
207
|
* It will also disconnect if called when the meeting has ended
|
|
160
208
|
* @returns {Promise}
|
|
161
209
|
*/
|
|
162
210
|
async updatePSDataChannel() {
|
|
211
|
+
this._updatePSDataChannelSequence = (this._updatePSDataChannelSequence || 0) + 1;
|
|
212
|
+
const invocationSequence = this._updatePSDataChannelSequence;
|
|
213
|
+
|
|
163
214
|
const meeting = this.webex.meetings.getMeetingByType(_ID_, this.meetingId);
|
|
164
215
|
const isPracticeSession = meeting?.isJoined() && this.isJoinPracticeSessionDataChannel();
|
|
165
216
|
|
|
@@ -174,7 +225,7 @@ const Webinar = WebexPlugin.extend({
|
|
|
174
225
|
meeting?.locusInfo || {};
|
|
175
226
|
|
|
176
227
|
// @ts-ignore
|
|
177
|
-
|
|
228
|
+
let practiceSessionDatachannelToken = this.webex.internal.llm.getDatachannelToken(
|
|
178
229
|
DataChannelTokenType.PracticeSession
|
|
179
230
|
);
|
|
180
231
|
|
|
@@ -229,6 +280,29 @@ const Webinar = WebexPlugin.extend({
|
|
|
229
280
|
this._pendingOnlineListener = null;
|
|
230
281
|
}
|
|
231
282
|
|
|
283
|
+
const refreshedPracticeSessionToken = await this.ensurePracticeSessionDatachannelToken(meeting);
|
|
284
|
+
|
|
285
|
+
const latestPracticeSessionDatachannelUrl = get(
|
|
286
|
+
meeting,
|
|
287
|
+
'locusInfo.info.practiceSessionDatachannelUrl'
|
|
288
|
+
);
|
|
289
|
+
const isStillPracticeSession = meeting?.isJoined() && this.isJoinPracticeSessionDataChannel();
|
|
290
|
+
|
|
291
|
+
// Skip stale invocations after async refresh to avoid reconnecting a session
|
|
292
|
+
// that was already updated/cleaned by a newer state transition.
|
|
293
|
+
if (
|
|
294
|
+
invocationSequence !== this._updatePSDataChannelSequence ||
|
|
295
|
+
!isStillPracticeSession ||
|
|
296
|
+
!latestPracticeSessionDatachannelUrl ||
|
|
297
|
+
latestPracticeSessionDatachannelUrl !== practiceSessionDatachannelUrl
|
|
298
|
+
) {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (refreshedPracticeSessionToken) {
|
|
303
|
+
practiceSessionDatachannelToken = refreshedPracticeSessionToken;
|
|
304
|
+
}
|
|
305
|
+
|
|
232
306
|
// @ts-ignore - Fix type
|
|
233
307
|
return this.webex.internal.llm
|
|
234
308
|
.registerAndConnect(
|
|
@@ -55,6 +55,27 @@ describe('plugin-meetings', () => {
|
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
describe('#locusUrlUpdate', () => {
|
|
59
|
+
it('should update the locusUrl property', () => {
|
|
60
|
+
const testLocusUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id';
|
|
61
|
+
|
|
62
|
+
aiEnableRequest.locusUrlUpdate(testLocusUrl);
|
|
63
|
+
|
|
64
|
+
assert.equal(aiEnableRequest.locusUrl, testLocusUrl);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should handle updating locusUrl multiple times', () => {
|
|
68
|
+
const firstUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id-1';
|
|
69
|
+
const secondUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id-2';
|
|
70
|
+
|
|
71
|
+
aiEnableRequest.locusUrlUpdate(firstUrl);
|
|
72
|
+
assert.equal(aiEnableRequest.locusUrl, firstUrl);
|
|
73
|
+
|
|
74
|
+
aiEnableRequest.locusUrlUpdate(secondUrl);
|
|
75
|
+
assert.equal(aiEnableRequest.locusUrl, secondUrl);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
58
79
|
describe('#selfParticipantIdUpdate', () => {
|
|
59
80
|
it('should update the selfParticipantId property', () => {
|
|
60
81
|
const testSelfParticipantId = 'participant-123';
|
|
@@ -254,6 +275,71 @@ describe('plugin-meetings', () => {
|
|
|
254
275
|
sinon.assert.notCalled(triggerSpy);
|
|
255
276
|
});
|
|
256
277
|
|
|
278
|
+
it('should not trigger event when locusUrl does not match', () => {
|
|
279
|
+
const testLocusUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id';
|
|
280
|
+
const differentLocusUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/different-id';
|
|
281
|
+
|
|
282
|
+
aiEnableRequest.locusUrl = testLocusUrl;
|
|
283
|
+
|
|
284
|
+
// Reset the spy after setting locusUrl to avoid counting property change events
|
|
285
|
+
triggerSpy.resetHistory();
|
|
286
|
+
|
|
287
|
+
aiEnableRequest.listenToApprovalRequests();
|
|
288
|
+
|
|
289
|
+
const event = {
|
|
290
|
+
data: {
|
|
291
|
+
locusUrl: differentLocusUrl,
|
|
292
|
+
approval: {
|
|
293
|
+
resourceType: AI_ENABLE_REQUEST.RESOURCE_TYPE,
|
|
294
|
+
receivers: [{participantId: testSelfParticipantId}],
|
|
295
|
+
initiator: {participantId: testInitiatorId},
|
|
296
|
+
actionType: AI_ENABLE_REQUEST.ACTION_TYPE.REQUESTED,
|
|
297
|
+
url: testUrl,
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
webex.internal.mercury.emit(`event:${LOCUSEVENT.APPROVAL_REQUEST}`, event);
|
|
303
|
+
|
|
304
|
+
sinon.assert.notCalled(triggerSpy);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('should trigger event when locusUrl matches', () => {
|
|
308
|
+
const testLocusUrl = 'https://locus-a.wbx2.com/locus/api/v1/loci/test-id';
|
|
309
|
+
|
|
310
|
+
aiEnableRequest.locusUrl = testLocusUrl;
|
|
311
|
+
|
|
312
|
+
// Reset the spy after setting locusUrl to avoid counting property change events
|
|
313
|
+
triggerSpy.resetHistory();
|
|
314
|
+
|
|
315
|
+
aiEnableRequest.listenToApprovalRequests();
|
|
316
|
+
|
|
317
|
+
const event = {
|
|
318
|
+
data: {
|
|
319
|
+
locusUrl: testLocusUrl,
|
|
320
|
+
approval: {
|
|
321
|
+
resourceType: AI_ENABLE_REQUEST.RESOURCE_TYPE,
|
|
322
|
+
receivers: [{participantId: testSelfParticipantId}],
|
|
323
|
+
initiator: {participantId: testInitiatorId},
|
|
324
|
+
actionType: AI_ENABLE_REQUEST.ACTION_TYPE.REQUESTED,
|
|
325
|
+
url: testUrl,
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
webex.internal.mercury.emit(`event:${LOCUSEVENT.APPROVAL_REQUEST}`, event);
|
|
331
|
+
|
|
332
|
+
sinon.assert.calledOnce(triggerSpy);
|
|
333
|
+
sinon.assert.calledWith(triggerSpy, AI_ENABLE_REQUEST.EVENTS.APPROVAL_REQUEST_ARRIVED, {
|
|
334
|
+
actionType: AI_ENABLE_REQUEST.ACTION_TYPE.REQUESTED,
|
|
335
|
+
isApprover: true,
|
|
336
|
+
isInitiator: false,
|
|
337
|
+
initiatorId: testInitiatorId,
|
|
338
|
+
approverId: testSelfParticipantId,
|
|
339
|
+
url: testUrl,
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
257
343
|
it('should handle events with different action types', () => {
|
|
258
344
|
aiEnableRequest.listenToApprovalRequests();
|
|
259
345
|
|
|
@@ -217,10 +217,14 @@ describe('plugin-meetings', () => {
|
|
|
217
217
|
locusParticipantsUpdate: sinon.stub(),
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
-
const locusData = {
|
|
220
|
+
const locusData = {participants: [{id: 'participant-1'}], sequence: {entries: [123]}};
|
|
221
221
|
const result = breakout.parseRoster(locusData);
|
|
222
222
|
|
|
223
|
-
assert.calledOnceWithExactly(breakout.members.locusParticipantsUpdate,
|
|
223
|
+
assert.calledOnceWithExactly(breakout.members.locusParticipantsUpdate, {
|
|
224
|
+
participants: [{id: 'participant-1'}],
|
|
225
|
+
isReplace: true,
|
|
226
|
+
});
|
|
227
|
+
assert.equal(breakout.breakoutRosterLocus, locusData);
|
|
224
228
|
assert.equal(result, undefined);
|
|
225
229
|
});
|
|
226
230
|
it('not call locusParticipantsUpdate if sequence is expired', () => {
|
|
@@ -228,7 +232,7 @@ describe('plugin-meetings', () => {
|
|
|
228
232
|
locusParticipantsUpdate: sinon.stub(),
|
|
229
233
|
};
|
|
230
234
|
breakout.isNeedHandleRoster = sinon.stub().returns(false);
|
|
231
|
-
const locusData = {
|
|
235
|
+
const locusData = {participants: [{id: 'participant-1'}], sequence: {entries: [123]}};
|
|
232
236
|
breakout.parseRoster(locusData);
|
|
233
237
|
|
|
234
238
|
assert.notCalled(breakout.members.locusParticipantsUpdate);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ControlsOptionsManager from '@webex/plugin-meetings/src/controls-options-manager';
|
|
2
2
|
import Util from '@webex/plugin-meetings/src/controls-options-manager/util';
|
|
3
|
+
import ParameterError from '@webex/plugin-meetings/src/common/errors/parameter';
|
|
3
4
|
import sinon from 'sinon';
|
|
4
5
|
import {assert} from '@webex/test-helper-chai';
|
|
5
6
|
import { HTTP_VERBS } from '@webex/plugin-meetings/src/constants';
|
|
@@ -76,6 +77,19 @@ describe('plugin-meetings', () => {
|
|
|
76
77
|
|
|
77
78
|
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
78
79
|
});
|
|
80
|
+
|
|
81
|
+
it('should send setMuteOnEntry to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
82
|
+
manager.setDisplayHints(['ENABLE_MUTE_ON_ENTRY']);
|
|
83
|
+
manager.mainLocusUrl = 'test/main';
|
|
84
|
+
|
|
85
|
+
const result = manager.setMuteOnEntry(true);
|
|
86
|
+
|
|
87
|
+
assert.calledWith(request.request, { uri: 'test/id/controls',
|
|
88
|
+
body: { muteOnEntry: { enabled: true } },
|
|
89
|
+
method: HTTP_VERBS.PATCH});
|
|
90
|
+
|
|
91
|
+
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
92
|
+
});
|
|
79
93
|
});
|
|
80
94
|
|
|
81
95
|
describe('setDisallowUnmute', () => {
|
|
@@ -118,6 +132,19 @@ describe('plugin-meetings', () => {
|
|
|
118
132
|
|
|
119
133
|
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
120
134
|
});
|
|
135
|
+
|
|
136
|
+
it('should send setDisallowUnmute to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
137
|
+
manager.setDisplayHints(['ENABLE_HARD_MUTE']);
|
|
138
|
+
manager.mainLocusUrl = 'test/main';
|
|
139
|
+
|
|
140
|
+
const result = manager.setDisallowUnmute(true);
|
|
141
|
+
|
|
142
|
+
assert.calledWith(request.request, { uri: 'test/id/controls',
|
|
143
|
+
body: { disallowUnmute: { enabled: true } },
|
|
144
|
+
method: HTTP_VERBS.PATCH});
|
|
145
|
+
|
|
146
|
+
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
147
|
+
});
|
|
121
148
|
});
|
|
122
149
|
});
|
|
123
150
|
|
|
@@ -138,6 +165,18 @@ describe('plugin-meetings', () => {
|
|
|
138
165
|
});
|
|
139
166
|
});
|
|
140
167
|
|
|
168
|
+
it('should reject with ParameterError when locusUrl is not set', () => {
|
|
169
|
+
const noLocusManager = new ControlsOptionsManager(request);
|
|
170
|
+
|
|
171
|
+
const result = noLocusManager.update({scope: 'audio', properties: {muted: true}});
|
|
172
|
+
|
|
173
|
+
assert.notCalled(request.request);
|
|
174
|
+
return assert.isRejected(result).then((err) => {
|
|
175
|
+
assert.instanceOf(err, ParameterError);
|
|
176
|
+
assert.match(err.message, /locusUrl.*must be defined/);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
141
180
|
it('should throw an error if the scope is not supported', () => {
|
|
142
181
|
const scope = 'invalid';
|
|
143
182
|
|
|
@@ -203,7 +242,7 @@ describe('plugin-meetings', () => {
|
|
|
203
242
|
});
|
|
204
243
|
});
|
|
205
244
|
|
|
206
|
-
it('should
|
|
245
|
+
it('should send audio controls to locusUrl without authorizingLocusUrl and non-audio to mainLocusUrl with authorizingLocusUrl when in breakout', () => {
|
|
207
246
|
const restorable = Util.canUpdate;
|
|
208
247
|
Util.canUpdate = sinon.stub().returns(true);
|
|
209
248
|
manager.mainLocusUrl = 'test/main';
|
|
@@ -213,15 +252,16 @@ describe('plugin-meetings', () => {
|
|
|
213
252
|
|
|
214
253
|
return manager.update(audio, reactions)
|
|
215
254
|
.then(() => {
|
|
255
|
+
// Audio controls go directly to current locusUrl (no cross-locus authorization)
|
|
216
256
|
assert.calledWith(request.request, {
|
|
217
|
-
uri: 'test/
|
|
257
|
+
uri: 'test/id/controls',
|
|
218
258
|
body: {
|
|
219
259
|
audio: audio.properties,
|
|
220
|
-
authorizingLocusUrl: 'test/id'
|
|
221
260
|
},
|
|
222
261
|
method: HTTP_VERBS.PATCH,
|
|
223
262
|
});
|
|
224
263
|
|
|
264
|
+
// Non-audio controls go to mainLocusUrl with authorizingLocusUrl
|
|
225
265
|
assert.calledWith(request.request, {
|
|
226
266
|
uri: 'test/main/controls',
|
|
227
267
|
body: {
|
|
@@ -234,6 +274,49 @@ describe('plugin-meetings', () => {
|
|
|
234
274
|
Util.canUpdate = restorable;
|
|
235
275
|
});
|
|
236
276
|
});
|
|
277
|
+
|
|
278
|
+
it('should send audio controls to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
279
|
+
const restorable = Util.canUpdate;
|
|
280
|
+
Util.canUpdate = sinon.stub().returns(true);
|
|
281
|
+
manager.mainLocusUrl = 'test/main';
|
|
282
|
+
|
|
283
|
+
const audio = {scope: 'audio', properties: {muted: true, disallowUnmute: false}};
|
|
284
|
+
|
|
285
|
+
return manager.update(audio)
|
|
286
|
+
.then(() => {
|
|
287
|
+
assert.calledWith(request.request, {
|
|
288
|
+
uri: 'test/id/controls',
|
|
289
|
+
body: {
|
|
290
|
+
audio: audio.properties,
|
|
291
|
+
},
|
|
292
|
+
method: HTTP_VERBS.PATCH,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
Util.canUpdate = restorable;
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('should send non-audio controls to mainLocusUrl with authorizingLocusUrl when in breakout', () => {
|
|
300
|
+
const restorable = Util.canUpdate;
|
|
301
|
+
Util.canUpdate = sinon.stub().returns(true);
|
|
302
|
+
manager.mainLocusUrl = 'test/main';
|
|
303
|
+
|
|
304
|
+
const reactions = {scope: 'reactions', properties: {enabled: true}};
|
|
305
|
+
|
|
306
|
+
return manager.update(reactions)
|
|
307
|
+
.then(() => {
|
|
308
|
+
assert.calledWith(request.request, {
|
|
309
|
+
uri: 'test/main/controls',
|
|
310
|
+
body: {
|
|
311
|
+
reactions: reactions.properties,
|
|
312
|
+
authorizingLocusUrl: 'test/id',
|
|
313
|
+
},
|
|
314
|
+
method: HTTP_VERBS.PATCH,
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
Util.canUpdate = restorable;
|
|
318
|
+
});
|
|
319
|
+
});
|
|
237
320
|
});
|
|
238
321
|
|
|
239
322
|
describe('Mute/Unmute All', () => {
|
|
@@ -252,6 +335,18 @@ describe('plugin-meetings', () => {
|
|
|
252
335
|
})
|
|
253
336
|
});
|
|
254
337
|
|
|
338
|
+
it('should reject with ParameterError when locusUrl is not set', () => {
|
|
339
|
+
const noLocusManager = new ControlsOptionsManager(request);
|
|
340
|
+
|
|
341
|
+
const result = noLocusManager.setMuteAll(true, true, true);
|
|
342
|
+
|
|
343
|
+
assert.notCalled(request.request);
|
|
344
|
+
return assert.isRejected(result).then((err) => {
|
|
345
|
+
assert.instanceOf(err, ParameterError);
|
|
346
|
+
assert.match(err.message, /locusUrl.*must be defined/);
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
|
|
255
350
|
it('rejects when correct display hint is not present mutedEnabled=false', () => {
|
|
256
351
|
const result = manager.setMuteAll(false, false, false);
|
|
257
352
|
|
|
@@ -340,14 +435,27 @@ describe('plugin-meetings', () => {
|
|
|
340
435
|
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
341
436
|
});
|
|
342
437
|
|
|
343
|
-
it('
|
|
438
|
+
it('should send setMuteAll to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
344
439
|
manager.setDisplayHints(['MUTE_ALL', 'DISABLE_HARD_MUTE', 'DISABLE_MUTE_ON_ENTRY']);
|
|
345
440
|
manager.mainLocusUrl = `test/main`;
|
|
346
441
|
|
|
347
442
|
const result = manager.setMuteAll(true, true, true, ['attendee']);
|
|
348
443
|
|
|
349
|
-
assert.calledWith(request.request, { uri: 'test/
|
|
350
|
-
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] }
|
|
444
|
+
assert.calledWith(request.request, { uri: 'test/id/controls',
|
|
445
|
+
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] } },
|
|
446
|
+
method: HTTP_VERBS.PATCH});
|
|
447
|
+
|
|
448
|
+
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should send setMuteAll with PANELIST role to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
452
|
+
manager.setDisplayHints(['MUTE_ALL', 'ENABLE_HARD_MUTE', 'ENABLE_MUTE_ON_ENTRY']);
|
|
453
|
+
manager.mainLocusUrl = `test/main`;
|
|
454
|
+
|
|
455
|
+
const result = manager.setMuteAll(true, true, true, ['PANELIST']);
|
|
456
|
+
|
|
457
|
+
assert.calledWith(request.request, { uri: 'test/id/controls',
|
|
458
|
+
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['PANELIST'] } },
|
|
351
459
|
method: HTTP_VERBS.PATCH});
|
|
352
460
|
|
|
353
461
|
assert.deepEqual(result, request.request.firstCall.returnValue);
|