@webex/plugin-meetings 3.0.0-beta.25 → 3.0.0-beta.27
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/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +92 -2
- package/dist/breakouts/index.js.map +1 -1
- package/dist/constants.js +5 -1
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +14 -0
- package/dist/controls-options-manager/constants.js.map +1 -0
- package/dist/controls-options-manager/enums.js +15 -0
- package/dist/controls-options-manager/enums.js.map +1 -0
- package/dist/controls-options-manager/index.js +203 -0
- package/dist/controls-options-manager/index.js.map +1 -0
- package/dist/controls-options-manager/util.js +28 -0
- package/dist/controls-options-manager/util.js.map +1 -0
- package/dist/meeting/in-meeting-actions.js +8 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +61 -13
- package/dist/meeting/index.js.map +1 -1
- package/dist/meetings/util.js +17 -0
- package/dist/meetings/util.js.map +1 -1
- package/dist/types/constants.d.ts +4 -0
- package/dist/types/controls-options-manager/constants.d.ts +4 -0
- package/dist/types/controls-options-manager/enums.d.ts +5 -0
- package/dist/types/controls-options-manager/index.d.ts +120 -0
- package/dist/types/controls-options-manager/util.d.ts +7 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +8 -0
- package/dist/types/meeting/index.d.ts +18 -0
- package/package.json +18 -18
- package/src/breakouts/index.ts +73 -1
- package/src/constants.ts +4 -0
- package/src/controls-options-manager/constants.ts +5 -0
- package/src/controls-options-manager/enums.ts +6 -0
- package/src/controls-options-manager/index.ts +183 -0
- package/src/controls-options-manager/util.ts +20 -0
- package/src/meeting/in-meeting-actions.ts +16 -0
- package/src/meeting/index.ts +50 -0
- package/src/meetings/util.ts +24 -0
- package/test/integration/spec/converged-space-meetings.js +176 -0
- package/test/unit/spec/breakouts/index.ts +76 -0
- package/test/unit/spec/controls-options-manager/index.js +124 -0
- package/test/unit/spec/controls-options-manager/util.js +66 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -0
- package/test/unit/spec/meeting/index.js +15 -0
- package/test/utils/constants.js +9 -0
- package/test/utils/testUtils.js +13 -6
- package/test/utils/webex-config.js +4 -0
- package/test/utils/webex-test-users.js +6 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {DISPLAY_HINTS} from '../constants';
|
|
2
|
+
|
|
3
|
+
const canSetMuteOnEntry = (displayHints: Array<string>): boolean =>
|
|
4
|
+
displayHints.includes(DISPLAY_HINTS.ENABLE_MUTE_ON_ENTRY);
|
|
5
|
+
|
|
6
|
+
const canSetDisallowUnmute = (displayHints: Array<string>): boolean =>
|
|
7
|
+
displayHints.includes(DISPLAY_HINTS.ENABLE_HARD_MUTE);
|
|
8
|
+
|
|
9
|
+
const canUnsetMuteOnEntry = (displayHints: Array<string>): boolean =>
|
|
10
|
+
displayHints.includes(DISPLAY_HINTS.DISABLE_MUTE_ON_ENTRY);
|
|
11
|
+
|
|
12
|
+
const canUnsetDisallowUnmute = (displayHints: Array<string>): boolean =>
|
|
13
|
+
displayHints.includes(DISPLAY_HINTS.DISABLE_HARD_MUTE);
|
|
14
|
+
|
|
15
|
+
export default {
|
|
16
|
+
canSetMuteOnEntry,
|
|
17
|
+
canSetDisallowUnmute,
|
|
18
|
+
canUnsetMuteOnEntry,
|
|
19
|
+
canUnsetDisallowUnmute,
|
|
20
|
+
};
|
|
@@ -13,6 +13,10 @@ interface IInMeetingActions {
|
|
|
13
13
|
canAdmitParticipant?: boolean;
|
|
14
14
|
canLock?: boolean;
|
|
15
15
|
canUnlock?: boolean;
|
|
16
|
+
canSetMuteOnEntry?: boolean;
|
|
17
|
+
canUnsetMuteOnEntry?: boolean;
|
|
18
|
+
canSetDisallowUnmute?: boolean;
|
|
19
|
+
canUnsetDisallowUnmute?: boolean;
|
|
16
20
|
canAssignHost?: boolean;
|
|
17
21
|
canStartRecording?: boolean;
|
|
18
22
|
canPauseRecording?: boolean;
|
|
@@ -59,6 +63,14 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
|
59
63
|
|
|
60
64
|
canStopRecording = null;
|
|
61
65
|
|
|
66
|
+
canSetMuteOnEntry = null;
|
|
67
|
+
|
|
68
|
+
canUnsetMuteOnEntry = null;
|
|
69
|
+
|
|
70
|
+
canSetDisallowUnmute = null;
|
|
71
|
+
|
|
72
|
+
canUnsetDisallowUnmute = null;
|
|
73
|
+
|
|
62
74
|
canRaiseHand = null;
|
|
63
75
|
|
|
64
76
|
canLowerAllHands = null;
|
|
@@ -99,6 +111,10 @@ export default class InMeetingActions implements IInMeetingActions {
|
|
|
99
111
|
canLock: this.canLock,
|
|
100
112
|
canUnlock: this.canUnlock,
|
|
101
113
|
canAssignHost: this.canAssignHost,
|
|
114
|
+
canSetMuteOnEntry: this.canSetMuteOnEntry,
|
|
115
|
+
canUnsetMuteOnEntry: this.canUnsetMuteOnEntry,
|
|
116
|
+
canSetDisallowUnmute: this.canSetDisallowUnmute,
|
|
117
|
+
canUnsetDisallowUnmute: this.canUnsetDisallowUnmute,
|
|
102
118
|
canStartRecording: this.canStartRecording,
|
|
103
119
|
canPauseRecording: this.canPauseRecording,
|
|
104
120
|
canResumeRecording: this.canResumeRecording,
|
package/src/meeting/index.ts
CHANGED
|
@@ -37,6 +37,7 @@ import MeetingRequest from './request';
|
|
|
37
37
|
import Members from '../members/index';
|
|
38
38
|
import MeetingUtil from './util';
|
|
39
39
|
import RecordingUtil from '../recording-controller/util';
|
|
40
|
+
import ControlsOptionsUtil from '../controls-options-manager/util';
|
|
40
41
|
import MediaUtil from '../media/util';
|
|
41
42
|
import Transcription from '../transcription';
|
|
42
43
|
import {Reactions, SkinTones} from '../reactions/reactions';
|
|
@@ -106,6 +107,7 @@ import Breakouts from '../breakouts';
|
|
|
106
107
|
import InMeetingActions from './in-meeting-actions';
|
|
107
108
|
import {REACTION_RELAY_TYPES} from '../reactions/constants';
|
|
108
109
|
import RecordingController from '../recording-controller';
|
|
110
|
+
import ControlsOptionsManager from '../controls-options-manager';
|
|
109
111
|
|
|
110
112
|
const {isBrowser} = BrowserDetection();
|
|
111
113
|
|
|
@@ -485,6 +487,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
485
487
|
recording: any;
|
|
486
488
|
remoteMediaManager: RemoteMediaManager | null;
|
|
487
489
|
recordingController: RecordingController;
|
|
490
|
+
controlsOptionsManager: ControlsOptionsManager;
|
|
488
491
|
requiredCaptcha: any;
|
|
489
492
|
receiveSlotManager: ReceiveSlotManager;
|
|
490
493
|
shareStatus: string;
|
|
@@ -1098,6 +1101,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1098
1101
|
displayHints: [],
|
|
1099
1102
|
});
|
|
1100
1103
|
|
|
1104
|
+
/**
|
|
1105
|
+
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1106
|
+
* @instance
|
|
1107
|
+
* @type {ControlsOptionsManager}
|
|
1108
|
+
* @public
|
|
1109
|
+
* @memberof Meeting
|
|
1110
|
+
*/
|
|
1111
|
+
this.controlsOptionsManager = new ControlsOptionsManager(this.meetingRequest, {
|
|
1112
|
+
locusUrl: this.locusInfo?.url,
|
|
1113
|
+
displayHints: [],
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1101
1116
|
this.setUpLocusInfoListeners();
|
|
1102
1117
|
this.locusInfo.init(attrs.locus ? attrs.locus : {});
|
|
1103
1118
|
this.hasJoinedOnce = false;
|
|
@@ -2187,6 +2202,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2187
2202
|
this.locusUrl = payload;
|
|
2188
2203
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
2189
2204
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
2205
|
+
this.controlsOptionsManager.setLocusUrl(this.locusUrl);
|
|
2190
2206
|
});
|
|
2191
2207
|
}
|
|
2192
2208
|
|
|
@@ -2203,6 +2219,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2203
2219
|
this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_SERVICES, (payload) => {
|
|
2204
2220
|
this.recordingController.setServiceUrl(payload?.services?.record?.url);
|
|
2205
2221
|
this.recordingController.setSessionId(this.locusInfo?.fullState?.sessionId);
|
|
2222
|
+
this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
|
|
2206
2223
|
});
|
|
2207
2224
|
}
|
|
2208
2225
|
|
|
@@ -2252,6 +2269,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2252
2269
|
canAdmitParticipant: MeetingUtil.canAdmitParticipant(payload.info.userDisplayHints),
|
|
2253
2270
|
canLock: MeetingUtil.canUserLock(payload.info.userDisplayHints),
|
|
2254
2271
|
canUnlock: MeetingUtil.canUserUnlock(payload.info.userDisplayHints),
|
|
2272
|
+
canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(
|
|
2273
|
+
payload.info.userDisplayHints
|
|
2274
|
+
),
|
|
2275
|
+
canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(
|
|
2276
|
+
payload.info.userDisplayHints
|
|
2277
|
+
),
|
|
2278
|
+
canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(payload.info.userDisplayHints),
|
|
2279
|
+
canUnsetMuteOnEntry: ControlsOptionsUtil.canUnsetMuteOnEntry(
|
|
2280
|
+
payload.info.userDisplayHints
|
|
2281
|
+
),
|
|
2255
2282
|
canStartRecording: RecordingUtil.canUserStart(payload.info.userDisplayHints),
|
|
2256
2283
|
canStopRecording: RecordingUtil.canUserStop(payload.info.userDisplayHints),
|
|
2257
2284
|
canPauseRecording: RecordingUtil.canUserPause(payload.info.userDisplayHints),
|
|
@@ -2288,6 +2315,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2288
2315
|
});
|
|
2289
2316
|
|
|
2290
2317
|
this.recordingController.setDisplayHints(payload.info.userDisplayHints);
|
|
2318
|
+
this.controlsOptionsManager.setDisplayHints(payload.info.userDisplayHints);
|
|
2291
2319
|
|
|
2292
2320
|
if (changed) {
|
|
2293
2321
|
Trigger.trigger(
|
|
@@ -6131,6 +6159,28 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6131
6159
|
return this.recordingController.startRecording();
|
|
6132
6160
|
}
|
|
6133
6161
|
|
|
6162
|
+
/**
|
|
6163
|
+
* set the mute on entry flag for participants if you're the host
|
|
6164
|
+
* @returns {Promise}
|
|
6165
|
+
* @param {boolean} enabled
|
|
6166
|
+
* @public
|
|
6167
|
+
* @memberof Meeting
|
|
6168
|
+
*/
|
|
6169
|
+
public setMuteOnEntry(enabled: boolean) {
|
|
6170
|
+
return this.controlsOptionsManager.setMuteOnEntry(enabled);
|
|
6171
|
+
}
|
|
6172
|
+
|
|
6173
|
+
/**
|
|
6174
|
+
* set the disallow unmute flag for participants if you're the host
|
|
6175
|
+
* @returns {Promise}
|
|
6176
|
+
* @param {boolean} enabled
|
|
6177
|
+
* @public
|
|
6178
|
+
* @memberof Meeting
|
|
6179
|
+
*/
|
|
6180
|
+
public setDisallowUnmute(enabled: boolean) {
|
|
6181
|
+
return this.controlsOptionsManager.setDisallowUnmute(enabled);
|
|
6182
|
+
}
|
|
6183
|
+
|
|
6134
6184
|
/**
|
|
6135
6185
|
* End the recording of this meeting
|
|
6136
6186
|
* @returns {Promise}
|
package/src/meetings/util.ts
CHANGED
|
@@ -58,12 +58,36 @@ MeetingsUtil.handleRoapMercury = (envelope, meetingCollection) => {
|
|
|
58
58
|
errorCause,
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
|
|
62
|
+
|
|
61
63
|
meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
|
|
64
|
+
|
|
65
|
+
if (mediaServer) {
|
|
66
|
+
meeting.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
|
|
67
|
+
}
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
};
|
|
66
72
|
|
|
73
|
+
MeetingsUtil.getMediaServer = (sdp) => {
|
|
74
|
+
let mediaServer;
|
|
75
|
+
|
|
76
|
+
// Attempt to collect the media server from the roap message.
|
|
77
|
+
try {
|
|
78
|
+
mediaServer = sdp
|
|
79
|
+
.split('\r\n')
|
|
80
|
+
.find((line) => line.startsWith('o='))
|
|
81
|
+
.split(' ')
|
|
82
|
+
.shift()
|
|
83
|
+
.replace('o=', '');
|
|
84
|
+
} catch {
|
|
85
|
+
mediaServer = undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return mediaServer;
|
|
89
|
+
};
|
|
90
|
+
|
|
67
91
|
MeetingsUtil.checkForCorrelationId = (deviceUrl, locus) => {
|
|
68
92
|
let devices = [];
|
|
69
93
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import 'jsdom-global/register';
|
|
3
|
+
import {assert} from '@webex/test-helper-chai';
|
|
4
|
+
import {skipInNode} from '@webex/test-helper-mocha';
|
|
5
|
+
import BrowserDetection from '@webex/plugin-meetings/dist/common/browser-detection';
|
|
6
|
+
|
|
7
|
+
import {MEDIA_SERVERS} from '../../utils/constants';
|
|
8
|
+
import testUtils from '../../utils/testUtils';
|
|
9
|
+
import webexTestUsers from '../../utils/webex-test-users';
|
|
10
|
+
|
|
11
|
+
config();
|
|
12
|
+
|
|
13
|
+
skipInNode(describe)('plugin-meetings', () => {
|
|
14
|
+
const {isBrowser} = BrowserDetection();
|
|
15
|
+
|
|
16
|
+
// `addMedia()` fails on FF, this needs to be debuged and fixed in a later change
|
|
17
|
+
if (!isBrowser('firefox')) {
|
|
18
|
+
describe('converged-space-meeting', () => {
|
|
19
|
+
let shouldSkip = false;
|
|
20
|
+
let users, alice, bob, chris;
|
|
21
|
+
let meeting = null;
|
|
22
|
+
let space = null;
|
|
23
|
+
let mediaReadyListener = null;
|
|
24
|
+
|
|
25
|
+
before('setup users', async () => {
|
|
26
|
+
const userSet = await webexTestUsers.generateTestUsers({
|
|
27
|
+
count: 3,
|
|
28
|
+
whistler: process.env.WHISTLER || process.env.JENKINS,
|
|
29
|
+
config
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
users = userSet;
|
|
33
|
+
alice = users[0];
|
|
34
|
+
bob = users[1];
|
|
35
|
+
chris = users[2];
|
|
36
|
+
alice.name = 'alice';
|
|
37
|
+
bob.name = 'bob';
|
|
38
|
+
chris.name = 'chris';
|
|
39
|
+
|
|
40
|
+
const aliceSync = testUtils.syncAndEndMeeting(alice);
|
|
41
|
+
const bobSync = testUtils.syncAndEndMeeting(bob);
|
|
42
|
+
const chrisSync = testUtils.syncAndEndMeeting(chris);
|
|
43
|
+
|
|
44
|
+
await aliceSync;
|
|
45
|
+
await bobSync;
|
|
46
|
+
await chrisSync;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Skip a test in this series if one failed.
|
|
50
|
+
// This beforeEach() instance function must use the `function` declaration to preserve the
|
|
51
|
+
// `this` context. `() => {}` will not generate the correct `this` context
|
|
52
|
+
beforeEach('check if should skip test', function() {
|
|
53
|
+
if (shouldSkip) {
|
|
54
|
+
this.skip();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Store to the describe scope if a test has failed for skipping.
|
|
59
|
+
// This beforeEach() instance function must use the `function` declaration to preserve the
|
|
60
|
+
// `this` context. `() => {}` will not generate the correct `this` context
|
|
61
|
+
afterEach('check if test failed', function() {
|
|
62
|
+
if (this.currentTest.state === 'failed') {
|
|
63
|
+
shouldSkip = true;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('user "alice" starts a space', async () => {
|
|
68
|
+
const conversation = await alice.webex.internal.conversation.create({
|
|
69
|
+
participants: [bob, chris],
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
assert.lengthOf(conversation.participants.items, 3);
|
|
73
|
+
assert.lengthOf(conversation.activities.items, 1);
|
|
74
|
+
|
|
75
|
+
space = conversation;
|
|
76
|
+
|
|
77
|
+
const destinationWithType = await alice.webex.meetings.meetingInfo.fetchMeetingInfo(space.url, 'CONVERSATION_URL');
|
|
78
|
+
const destinationWithoutType = await alice.webex.meetings.meetingInfo.fetchMeetingInfo(space.url);
|
|
79
|
+
|
|
80
|
+
assert.exists(destinationWithoutType);
|
|
81
|
+
assert.exists(destinationWithType);
|
|
82
|
+
assert.exists(destinationWithoutType.body.meetingNumber);
|
|
83
|
+
assert.exists(destinationWithType.body.meetingNumber);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('user "alice" starts a meeting', async () => {
|
|
87
|
+
const wait = testUtils.waitForEvents([{
|
|
88
|
+
scope: alice.webex.meetings,
|
|
89
|
+
event: 'meeting:added',
|
|
90
|
+
user: alice,
|
|
91
|
+
}]);
|
|
92
|
+
|
|
93
|
+
const createdMeeting = await testUtils.delayedPromise(alice.webex.meetings.create(space.url));
|
|
94
|
+
|
|
95
|
+
await wait;
|
|
96
|
+
|
|
97
|
+
assert.exists(createdMeeting);
|
|
98
|
+
|
|
99
|
+
meeting = createdMeeting;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('user "alice" joins the meeting', async () => {
|
|
103
|
+
const wait = testUtils.waitForEvents([
|
|
104
|
+
{scope: bob.webex.meetings, event: 'meeting:added', user: bob},
|
|
105
|
+
{scope: chris.webex.meetings, event: 'meeting:added', user: chris},
|
|
106
|
+
]);
|
|
107
|
+
|
|
108
|
+
await testUtils.delayedPromise(alice.meeting.join({enableMultistream: true}));
|
|
109
|
+
|
|
110
|
+
await wait;
|
|
111
|
+
|
|
112
|
+
assert.isTrue(!!alice.webex.meetings.meetingCollection.meetings[meeting.id].joinedWith);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('users "bob" and "chris" join the meeting', async () => {
|
|
116
|
+
await testUtils.waitForStateChange(alice.meeting, 'JOINED');
|
|
117
|
+
|
|
118
|
+
const bobIdle = testUtils.waitForStateChange(bob.meeting, 'IDLE');
|
|
119
|
+
const chrisIdle = testUtils.waitForStateChange(chris.meeting, 'IDLE');
|
|
120
|
+
|
|
121
|
+
await bobIdle;
|
|
122
|
+
await chrisIdle;
|
|
123
|
+
|
|
124
|
+
const bobJoined = testUtils.waitForStateChange(bob.meeting, 'JOINED');
|
|
125
|
+
const chrisJoined = testUtils.waitForStateChange(chris.meeting, 'JOINED');
|
|
126
|
+
const bobJoin = bob.meeting.join({enableMultistream: true});
|
|
127
|
+
const chrisJoin = chris.meeting.join({enableMultistream: true});
|
|
128
|
+
|
|
129
|
+
await bobJoin;
|
|
130
|
+
await chrisJoin;
|
|
131
|
+
await bobJoined;
|
|
132
|
+
await chrisJoined;
|
|
133
|
+
|
|
134
|
+
assert.exists(bob.meeting.joinedWith);
|
|
135
|
+
assert.exists(chris.meeting.joinedWith);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('users "alice", "bob", and "chris" add media', async () => {
|
|
139
|
+
mediaReadyListener = testUtils.waitForEvents([
|
|
140
|
+
{scope: alice.meeting, event: 'media:negotiated'},
|
|
141
|
+
{scope: bob.meeting, event: 'media:negotiated'},
|
|
142
|
+
{scope: chris.meeting, event: 'media:negotiated'},
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
const addMediaAlice = testUtils.addMedia(alice, {expectedMediaReadyTypes: ['local']});
|
|
146
|
+
const addMediaBob = testUtils.addMedia(bob, {expectedMediaReadyTypes: ['local']});
|
|
147
|
+
const addMediaChris = testUtils.addMedia(chris, {expectedMediaReadyTypes: ['local']});
|
|
148
|
+
|
|
149
|
+
await addMediaAlice;
|
|
150
|
+
await addMediaBob;
|
|
151
|
+
await addMediaChris;
|
|
152
|
+
|
|
153
|
+
assert.isTrue(alice.meeting.mediaProperties.mediaDirection.sendAudio);
|
|
154
|
+
assert.isTrue(alice.meeting.mediaProperties.mediaDirection.sendVideo);
|
|
155
|
+
assert.isTrue(alice.meeting.mediaProperties.mediaDirection.receiveAudio);
|
|
156
|
+
assert.isTrue(alice.meeting.mediaProperties.mediaDirection.receiveVideo);
|
|
157
|
+
assert.isTrue(bob.meeting.mediaProperties.mediaDirection.sendAudio);
|
|
158
|
+
assert.isTrue(bob.meeting.mediaProperties.mediaDirection.sendVideo);
|
|
159
|
+
assert.isTrue(bob.meeting.mediaProperties.mediaDirection.receiveAudio);
|
|
160
|
+
assert.isTrue(bob.meeting.mediaProperties.mediaDirection.receiveVideo);
|
|
161
|
+
assert.isTrue(chris.meeting.mediaProperties.mediaDirection.sendAudio);
|
|
162
|
+
assert.isTrue(chris.meeting.mediaProperties.mediaDirection.sendVideo);
|
|
163
|
+
assert.isTrue(chris.meeting.mediaProperties.mediaDirection.receiveAudio);
|
|
164
|
+
assert.isTrue(chris.meeting.mediaProperties.mediaDirection.receiveVideo);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it(`users "alice", "bob", and "chris" should be using the "${MEDIA_SERVERS.HOMER}" media server`, async () => {
|
|
168
|
+
await mediaReadyListener;
|
|
169
|
+
|
|
170
|
+
assert.equal(alice.meeting.mediaProperties.webrtcMediaConnection.mediaServer, MEDIA_SERVERS.HOMER);
|
|
171
|
+
assert.equal(bob.meeting.mediaProperties.webrtcMediaConnection.mediaServer, MEDIA_SERVERS.HOMER);
|
|
172
|
+
assert.equal(chris.meeting.mediaProperties.webrtcMediaConnection.mediaServer, MEDIA_SERVERS.HOMER);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
@@ -20,6 +20,9 @@ describe('plugin-meetings', () => {
|
|
|
20
20
|
webex.internal.llm.on = sinon.stub();
|
|
21
21
|
webex.internal.mercury.on = sinon.stub();
|
|
22
22
|
breakouts = new Breakouts({}, {parent: webex});
|
|
23
|
+
breakouts.locusUrl = 'locusUrl';
|
|
24
|
+
breakouts.breakoutServiceUrl = 'breakoutServiceUrl';
|
|
25
|
+
breakouts.url = 'url';
|
|
23
26
|
webex.request = sinon.stub().returns(Promise.resolve('REQUEST_RETURN_VALUE'));
|
|
24
27
|
});
|
|
25
28
|
|
|
@@ -289,5 +292,78 @@ describe('plugin-meetings', () => {
|
|
|
289
292
|
assert.equal(breakouts.isInMainSession, true);
|
|
290
293
|
});
|
|
291
294
|
});
|
|
295
|
+
|
|
296
|
+
describe('#breakoutServiceUrlUpdate', () => {
|
|
297
|
+
it('sets the breakoutService url', () => {
|
|
298
|
+
breakouts.breakoutServiceUrlUpdate('newBreakoutServiceUrl');
|
|
299
|
+
assert.equal(breakouts.breakoutServiceUrl, 'newBreakoutServiceUrl/breakout/');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('#toggleBreakout', () => {
|
|
304
|
+
it('enableBreakoutSession is undefined, run enableBreakouts then toggleBreakout', async() => {
|
|
305
|
+
breakouts.enableBreakoutSession = undefined;
|
|
306
|
+
breakouts.enableBreakouts = sinon.stub().resolves(({body: {
|
|
307
|
+
sessionId: 'sessionId',
|
|
308
|
+
groupId: 'groupId',
|
|
309
|
+
name: 'name',
|
|
310
|
+
current: true,
|
|
311
|
+
sessionType: 'sessionType',
|
|
312
|
+
url: 'url'
|
|
313
|
+
}}))
|
|
314
|
+
breakouts.updateBreakout = sinon.stub().resolves();
|
|
315
|
+
breakouts.doToggleBreakout = sinon.stub().resolves();
|
|
316
|
+
|
|
317
|
+
await breakouts.toggleBreakout(false);
|
|
318
|
+
assert.calledOnceWithExactly(breakouts.enableBreakouts);
|
|
319
|
+
assert.calledOnceWithExactly(breakouts.updateBreakout, {
|
|
320
|
+
sessionId: 'sessionId',
|
|
321
|
+
groupId: 'groupId',
|
|
322
|
+
name: 'name',
|
|
323
|
+
current: true,
|
|
324
|
+
sessionType: 'sessionType',
|
|
325
|
+
url: 'url'
|
|
326
|
+
});
|
|
327
|
+
assert.calledOnceWithExactly(breakouts.doToggleBreakout, false);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('enableBreakoutSession is exist, run toggleBreakout', async() => {
|
|
331
|
+
breakouts.enableBreakoutSession = true;
|
|
332
|
+
breakouts.doToggleBreakout = sinon.stub().resolves();
|
|
333
|
+
await breakouts.toggleBreakout(true);
|
|
334
|
+
assert.calledOnceWithExactly(breakouts.doToggleBreakout, true);
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
describe('enableBreakouts', () => {
|
|
339
|
+
it('makes the request as expected', async () => {
|
|
340
|
+
const result = await breakouts.enableBreakouts();
|
|
341
|
+
breakouts.set('breakoutServiceUrl', 'breakoutServiceUrl');
|
|
342
|
+
assert.calledOnceWithExactly(webex.request, {
|
|
343
|
+
method: 'POST',
|
|
344
|
+
uri: 'breakoutServiceUrl',
|
|
345
|
+
body: {
|
|
346
|
+
locusUrl: 'locusUrl'
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
assert.equal(result, 'REQUEST_RETURN_VALUE');
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
describe('doToggleBreakout', () => {
|
|
355
|
+
it('makes the request as expected', async () => {
|
|
356
|
+
const result = await breakouts.doToggleBreakout(true);
|
|
357
|
+
assert.calledOnceWithExactly(webex.request, {
|
|
358
|
+
method: 'PUT',
|
|
359
|
+
uri: 'url',
|
|
360
|
+
body: {
|
|
361
|
+
enableBreakoutSession: true
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
assert.equal(result, 'REQUEST_RETURN_VALUE');
|
|
366
|
+
});
|
|
367
|
+
});
|
|
292
368
|
});
|
|
293
369
|
});
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import ControlsOptionsManager from '@webex/plugin-meetings/src/controls-options-manager';
|
|
2
|
+
import sinon from 'sinon';
|
|
3
|
+
import {assert} from '@webex/test-helper-chai';
|
|
4
|
+
import { HTTP_VERBS } from '@webex/plugin-meetings/src/constants';
|
|
5
|
+
|
|
6
|
+
describe('plugin-meetings', () => {
|
|
7
|
+
describe('controls-options-manager tests', () => {
|
|
8
|
+
describe('index', () => {
|
|
9
|
+
let request;
|
|
10
|
+
|
|
11
|
+
describe('class tests', () => {
|
|
12
|
+
it('can set and extract new values later on', () => {
|
|
13
|
+
const manager = new ControlsOptionsManager({});
|
|
14
|
+
assert.isUndefined(manager.getLocusUrl());
|
|
15
|
+
manager.set({
|
|
16
|
+
locusUrl: 'test/id',
|
|
17
|
+
})
|
|
18
|
+
assert(manager.getLocusUrl(), 'test/id');
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('Mute On Entry', () => {
|
|
23
|
+
let manager;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
request = {
|
|
27
|
+
request: sinon.stub().returns(Promise.resolve()),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
manager = new ControlsOptionsManager(request);
|
|
31
|
+
|
|
32
|
+
manager.set({
|
|
33
|
+
locusUrl: 'test/id',
|
|
34
|
+
displayHints: [],
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('setMuteOnEntry', () => {
|
|
39
|
+
it('rejects when correct display hint is not present enabled=false', () => {
|
|
40
|
+
const result = manager.setMuteOnEntry(false);
|
|
41
|
+
|
|
42
|
+
assert.notCalled(request.request);
|
|
43
|
+
|
|
44
|
+
assert.isRejected(result);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('rejects when correct display hint is not present enabled=true', () => {
|
|
48
|
+
const result = manager.setMuteOnEntry(true);
|
|
49
|
+
|
|
50
|
+
assert.notCalled(request.request);
|
|
51
|
+
|
|
52
|
+
assert.isRejected(result);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('can set mute on entry when the display hint is available enabled=true', () => {
|
|
56
|
+
manager.setDisplayHints(['ENABLE_MUTE_ON_ENTRY']);
|
|
57
|
+
|
|
58
|
+
const result = manager.setMuteOnEntry(true);
|
|
59
|
+
|
|
60
|
+
assert.calledWith(request.request, { uri: 'test/id/controls',
|
|
61
|
+
body: { muteOnEntry: { enabled: true } },
|
|
62
|
+
method: HTTP_VERBS.PATCH});
|
|
63
|
+
|
|
64
|
+
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('can set mute on entry when the display hint is available enabled=false', () => {
|
|
68
|
+
manager.setDisplayHints(['DISABLE_MUTE_ON_ENTRY']);
|
|
69
|
+
|
|
70
|
+
const result = manager.setMuteOnEntry(false);
|
|
71
|
+
|
|
72
|
+
assert.calledWith(request.request, { uri: 'test/id/controls',
|
|
73
|
+
body: { muteOnEntry: { enabled: false } },
|
|
74
|
+
method: HTTP_VERBS.PATCH});
|
|
75
|
+
|
|
76
|
+
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('setDisallowUnmute', () => {
|
|
81
|
+
it('rejects when correct display hint is not present enabled=false', () => {
|
|
82
|
+
const result = manager.setDisallowUnmute(false);
|
|
83
|
+
|
|
84
|
+
assert.notCalled(request.request);
|
|
85
|
+
|
|
86
|
+
assert.isRejected(result);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('rejects when correct display hint is not present enabled=true', () => {
|
|
90
|
+
const result = manager.setDisallowUnmute(true);
|
|
91
|
+
|
|
92
|
+
assert.notCalled(request.request);
|
|
93
|
+
|
|
94
|
+
assert.isRejected(result);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('can set mute on entry when the display hint is available enabled=true', () => {
|
|
98
|
+
manager.setDisplayHints(['ENABLE_HARD_MUTE']);
|
|
99
|
+
|
|
100
|
+
const result = manager.setDisallowUnmute(true);
|
|
101
|
+
|
|
102
|
+
assert.calledWith(request.request, { uri: 'test/id/controls',
|
|
103
|
+
body: { disallowUnmute: { enabled: true } },
|
|
104
|
+
method: HTTP_VERBS.PATCH});
|
|
105
|
+
|
|
106
|
+
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('can set mute on entry when the display hint is available enabled=false', () => {
|
|
110
|
+
manager.setDisplayHints(['DISABLE_HARD_MUTE']);
|
|
111
|
+
|
|
112
|
+
const result = manager.setDisallowUnmute(false);
|
|
113
|
+
|
|
114
|
+
assert.calledWith(request.request, { uri: 'test/id/controls',
|
|
115
|
+
body: { disallowUnmute: { enabled: false } },
|
|
116
|
+
method: HTTP_VERBS.PATCH});
|
|
117
|
+
|
|
118
|
+
assert.deepEqual(result, request.request.firstCall.returnValue);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import ControlsOptionsUtil from '@webex/plugin-meetings/src/controls-options-manager/util';
|
|
2
|
+
import { assert } from 'chai';
|
|
3
|
+
|
|
4
|
+
describe('plugin-meetings', () => {
|
|
5
|
+
describe('controls-option-manager tests', () => {
|
|
6
|
+
describe('util tests', () => {
|
|
7
|
+
|
|
8
|
+
let locusInfo;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
locusInfo = {
|
|
12
|
+
parsedLocus: {
|
|
13
|
+
info: {
|
|
14
|
+
userDisplayHints: [],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('canUserSetMuteOnEntry', () => {
|
|
21
|
+
it('can set mute on entry enable', () => {
|
|
22
|
+
locusInfo.parsedLocus.info.userDisplayHints.push('ENABLE_MUTE_ON_ENTRY');
|
|
23
|
+
|
|
24
|
+
assert.equal(ControlsOptionsUtil.canSetMuteOnEntry(locusInfo.parsedLocus.info.userDisplayHints), true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('can set mute on entry disable', () => {
|
|
28
|
+
locusInfo.parsedLocus.info.userDisplayHints.push('DISABLE_MUTE_ON_ENTRY');
|
|
29
|
+
|
|
30
|
+
assert.equal(ControlsOptionsUtil.canUnsetMuteOnEntry(locusInfo.parsedLocus.info.userDisplayHints), true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('rejects when correct display hint is not present for setting mute on entry', () => {
|
|
34
|
+
assert.equal(ControlsOptionsUtil.canSetMuteOnEntry(locusInfo.parsedLocus.info.userDisplayHints), false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('rejects when correct display hint is not present for unsetting mute on entry', () => {
|
|
38
|
+
assert.equal(ControlsOptionsUtil.canUnsetMuteOnEntry(locusInfo.parsedLocus.info.userDisplayHints), false);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe('canSetDisallowUnmute', () => {
|
|
43
|
+
it('can set disallow unmute enable', () => {
|
|
44
|
+
locusInfo.parsedLocus.info.userDisplayHints.push('ENABLE_HARD_MUTE');
|
|
45
|
+
|
|
46
|
+
assert.equal(ControlsOptionsUtil.canSetDisallowUnmute(locusInfo.parsedLocus.info.userDisplayHints), true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('can set disallow unmute disable', () => {
|
|
50
|
+
locusInfo.parsedLocus.info.userDisplayHints.push('DISABLE_HARD_MUTE');
|
|
51
|
+
|
|
52
|
+
assert.equal(ControlsOptionsUtil.canUnsetDisallowUnmute(locusInfo.parsedLocus.info.userDisplayHints), true);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('rejects when correct display hint is not present', () => {
|
|
56
|
+
assert.equal(ControlsOptionsUtil.canSetDisallowUnmute(locusInfo.parsedLocus.info.userDisplayHints), false);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('rejects when correct display hint is not present', () => {
|
|
60
|
+
assert.equal(ControlsOptionsUtil.canUnsetDisallowUnmute(locusInfo.parsedLocus.info.userDisplayHints), false);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -13,6 +13,10 @@ describe('plugin-meetings', () => {
|
|
|
13
13
|
canStartRecording: null,
|
|
14
14
|
canPauseRecording: null,
|
|
15
15
|
canResumeRecording: null,
|
|
16
|
+
canSetMuteOnEntry: null,
|
|
17
|
+
canUnsetMuteOnEntry: null,
|
|
18
|
+
canSetDisallowUnmute: null,
|
|
19
|
+
canUnsetDisallowUnmute: null,
|
|
16
20
|
canStopRecording: null,
|
|
17
21
|
canRaiseHand: null,
|
|
18
22
|
canLowerAllHands: null,
|
|
@@ -51,6 +55,10 @@ describe('plugin-meetings', () => {
|
|
|
51
55
|
'canPauseRecording',
|
|
52
56
|
'canResumeRecording',
|
|
53
57
|
'canStopRecording',
|
|
58
|
+
'canSetMuteOnEntry',
|
|
59
|
+
'canUnsetMuteOnEntry',
|
|
60
|
+
'canSetDisallowUnmute',
|
|
61
|
+
'canUnsetDisallowUnmute',
|
|
54
62
|
'canRaiseHand',
|
|
55
63
|
'canLowerAllHands',
|
|
56
64
|
'canLowerSomeoneElsesHand',
|