@webex/plugin-meetings 3.7.0-next.1 → 3.7.0-next.11
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 +1 -1
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +10 -1
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/in-meeting-actions.js +11 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +287 -198
- 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 +4 -1
- package/dist/meetings/index.js.map +1 -1
- package/dist/members/util.js +4 -2
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/constants.js +3 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/reachability/clusterReachability.js +12 -11
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/recording-controller/enums.js +8 -4
- package/dist/recording-controller/enums.js.map +1 -1
- package/dist/recording-controller/index.js +18 -9
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/recording-controller/util.js +13 -9
- package/dist/recording-controller/util.js.map +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/constants.d.ts +8 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
- package/dist/types/meeting/index.d.ts +20 -0
- package/dist/types/meetings/index.d.ts +3 -0
- package/dist/types/members/util.d.ts +2 -0
- package/dist/types/metrics/constants.d.ts +2 -0
- package/dist/types/recording-controller/enums.d.ts +5 -2
- package/dist/types/recording-controller/index.d.ts +1 -0
- package/dist/types/recording-controller/util.d.ts +2 -1
- package/dist/webinar/index.js +39 -7
- package/dist/webinar/index.js.map +1 -1
- package/package.json +21 -21
- package/src/config.ts +1 -0
- package/src/constants.ts +10 -0
- package/src/meeting/in-meeting-actions.ts +21 -0
- package/src/meeting/index.ts +94 -0
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +6 -0
- package/src/members/util.ts +1 -0
- package/src/metrics/constants.ts +2 -0
- package/src/reachability/clusterReachability.ts +4 -1
- package/src/recording-controller/enums.ts +5 -2
- package/src/recording-controller/index.ts +17 -4
- package/src/recording-controller/util.ts +20 -5
- package/src/webinar/index.ts +40 -8
- package/test/unit/spec/meeting/in-meeting-actions.ts +13 -1
- package/test/unit/spec/meeting/index.js +72 -0
- package/test/unit/spec/meeting/utils.js +2 -0
- package/test/unit/spec/meetings/index.js +9 -5
- package/test/unit/spec/members/utils.js +95 -0
- package/test/unit/spec/reachability/clusterReachability.ts +7 -0
- package/test/unit/spec/recording-controller/index.js +61 -5
- package/test/unit/spec/recording-controller/util.js +39 -3
- package/test/unit/spec/webinar/index.ts +47 -0
package/src/meeting/util.ts
CHANGED
package/src/meetings/index.ts
CHANGED
|
@@ -155,6 +155,9 @@ export type BasicMeetingInformation = {
|
|
|
155
155
|
};
|
|
156
156
|
meetingInfo: any;
|
|
157
157
|
sessionCorrelationId: string;
|
|
158
|
+
roles: string[];
|
|
159
|
+
getCurUserType: () => string | null;
|
|
160
|
+
callStateForMetrics: CallStateForMetrics;
|
|
158
161
|
};
|
|
159
162
|
|
|
160
163
|
/**
|
|
@@ -1143,6 +1146,9 @@ export default class Meetings extends WebexPlugin {
|
|
|
1143
1146
|
sessionId: meeting.locusInfo?.fullState?.sessionId,
|
|
1144
1147
|
},
|
|
1145
1148
|
},
|
|
1149
|
+
roles: meeting.roles,
|
|
1150
|
+
callStateForMetrics: meeting.callStateForMetrics,
|
|
1151
|
+
getCurUserType: meeting.getCurUserType,
|
|
1146
1152
|
});
|
|
1147
1153
|
this.meetingCollection.delete(meeting.id);
|
|
1148
1154
|
Trigger.trigger(
|
package/src/members/util.ts
CHANGED
package/src/metrics/constants.ts
CHANGED
|
@@ -71,6 +71,8 @@ const BEHAVIORAL_METRICS = {
|
|
|
71
71
|
TURN_DISCOVERY_REQUIRES_OK: 'js_sdk_turn_discovery_requires_ok',
|
|
72
72
|
REACHABILITY_COMPLETED: 'js_sdk_reachability_completed',
|
|
73
73
|
WEBINAR_REGISTRATION_ERROR: 'js_sdk_webinar_registration_error',
|
|
74
|
+
GUEST_ENTERED_LOBBY: 'js_sdk_guest_entered_lobby',
|
|
75
|
+
GUEST_EXITED_LOBBY: 'js_sdk_guest_exited_lobby',
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
export {BEHAVIORAL_METRICS as default};
|
|
@@ -357,11 +357,14 @@ export class ClusterReachability extends EventsScope {
|
|
|
357
357
|
|
|
358
358
|
this.startTimestamp = performance.now();
|
|
359
359
|
|
|
360
|
+
// Set up the state change listeners before triggering the ICE gathering
|
|
361
|
+
const gatherIceCandidatePromise = this.gatherIceCandidates();
|
|
362
|
+
|
|
360
363
|
// not awaiting the next call on purpose, because we're not sending the offer anywhere and there won't be any answer
|
|
361
364
|
// we just need to make this call to trigger the ICE gathering process
|
|
362
365
|
this.pc.setLocalDescription(offer);
|
|
363
366
|
|
|
364
|
-
await
|
|
367
|
+
await gatherIceCandidatePromise;
|
|
365
368
|
} catch (error) {
|
|
366
369
|
LoggerProxy.logger.warn(`Reachability:ClusterReachability#start --> Error: `, error);
|
|
367
370
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import PermissionError from '../common/errors/permission';
|
|
2
|
+
import LoggerProxy from '../common/logs/logger-proxy';
|
|
2
3
|
import {CONTROLS, HTTP_VERBS, SELF_POLICY} from '../constants';
|
|
3
4
|
import MeetingRequest from '../meeting/request';
|
|
4
|
-
import RecordingAction from './enums';
|
|
5
|
+
import {RecordingAction, RecordingType} from './enums';
|
|
5
6
|
import Util from './util';
|
|
6
|
-
import LoggerProxy from '../common/logs/logger-proxy';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @description Recording manages the recording functionality of the meeting object, there should only be one instantation of recording per meeting
|
|
@@ -228,11 +228,12 @@ export default class RecordingController {
|
|
|
228
228
|
|
|
229
229
|
/**
|
|
230
230
|
* @param {RecordingAction} action
|
|
231
|
+
* @param {RecordingType} recordingType
|
|
231
232
|
* @private
|
|
232
233
|
* @memberof RecordingController
|
|
233
234
|
* @returns {Promise}
|
|
234
235
|
*/
|
|
235
|
-
private recordingService(action: RecordingAction): Promise<any> {
|
|
236
|
+
private recordingService(action: RecordingAction, recordingType: RecordingType): Promise<any> {
|
|
236
237
|
// @ts-ignore
|
|
237
238
|
return this.request.request({
|
|
238
239
|
body: {
|
|
@@ -242,6 +243,7 @@ export default class RecordingController {
|
|
|
242
243
|
recording: {
|
|
243
244
|
action: action.toLowerCase(),
|
|
244
245
|
},
|
|
246
|
+
recordingType,
|
|
245
247
|
},
|
|
246
248
|
uri: `${this.serviceUrl}/loci/${this.locusId}/recording`,
|
|
247
249
|
method: HTTP_VERBS.PUT,
|
|
@@ -276,14 +278,25 @@ export default class RecordingController {
|
|
|
276
278
|
* @returns {Promise}
|
|
277
279
|
*/
|
|
278
280
|
private recordingFacade(action: RecordingAction): Promise<any> {
|
|
281
|
+
const isPremiseRecordingEnabled = Util.isPremiseRecordingEnabled(
|
|
282
|
+
this.displayHints,
|
|
283
|
+
this.selfUserPolicies
|
|
284
|
+
);
|
|
279
285
|
LoggerProxy.logger.log(
|
|
280
286
|
`RecordingController:index#recordingFacade --> recording action [${action}]`
|
|
281
287
|
);
|
|
282
288
|
|
|
289
|
+
let recordingType: RecordingType;
|
|
290
|
+
if (isPremiseRecordingEnabled) {
|
|
291
|
+
recordingType = RecordingType.Premise;
|
|
292
|
+
} else {
|
|
293
|
+
recordingType = RecordingType.Cloud;
|
|
294
|
+
}
|
|
295
|
+
|
|
283
296
|
// assumes action is proper cased (i.e., Example)
|
|
284
297
|
if (Util?.[`canUser${action}`](this.displayHints, this.selfUserPolicies)) {
|
|
285
298
|
if (this.serviceUrl) {
|
|
286
|
-
return this.recordingService(action);
|
|
299
|
+
return this.recordingService(action, recordingType);
|
|
287
300
|
}
|
|
288
301
|
|
|
289
302
|
return this.recordingControls(action);
|
|
@@ -1,33 +1,47 @@
|
|
|
1
1
|
import {DISPLAY_HINTS, SELF_POLICY} from '../constants';
|
|
2
|
-
import RecordingAction from './enums';
|
|
2
|
+
import {RecordingAction} from './enums';
|
|
3
3
|
import MeetingUtil from '../meeting/util';
|
|
4
4
|
|
|
5
5
|
const canUserStart = (
|
|
6
6
|
displayHints: Array<string>,
|
|
7
7
|
userPolicies: Record<SELF_POLICY, boolean>
|
|
8
8
|
): boolean =>
|
|
9
|
-
displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START)
|
|
9
|
+
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START) ||
|
|
10
|
+
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START)) &&
|
|
10
11
|
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
|
|
11
12
|
|
|
12
13
|
const canUserPause = (
|
|
13
14
|
displayHints: Array<string>,
|
|
14
15
|
userPolicies: Record<SELF_POLICY, boolean>
|
|
15
16
|
): boolean =>
|
|
16
|
-
displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE)
|
|
17
|
+
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE) ||
|
|
18
|
+
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE)) &&
|
|
17
19
|
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
|
|
18
20
|
|
|
19
21
|
const canUserResume = (
|
|
20
22
|
displayHints: Array<string>,
|
|
21
23
|
userPolicies: Record<SELF_POLICY, boolean>
|
|
22
24
|
): boolean =>
|
|
23
|
-
displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME)
|
|
25
|
+
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME) ||
|
|
26
|
+
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) &&
|
|
24
27
|
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
|
|
25
28
|
|
|
26
29
|
const canUserStop = (
|
|
27
30
|
displayHints: Array<string>,
|
|
28
31
|
userPolicies: Record<SELF_POLICY, boolean>
|
|
29
32
|
): boolean =>
|
|
30
|
-
displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP)
|
|
33
|
+
(displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP) ||
|
|
34
|
+
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP)) &&
|
|
35
|
+
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
|
|
36
|
+
|
|
37
|
+
const isPremiseRecordingEnabled = (
|
|
38
|
+
displayHints: Array<string>,
|
|
39
|
+
userPolicies: Record<SELF_POLICY, boolean>
|
|
40
|
+
): boolean =>
|
|
41
|
+
(displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START) ||
|
|
42
|
+
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE) ||
|
|
43
|
+
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP) ||
|
|
44
|
+
displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) &&
|
|
31
45
|
MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
|
|
32
46
|
|
|
33
47
|
const extractLocusId = (url: string) => {
|
|
@@ -70,6 +84,7 @@ export default {
|
|
|
70
84
|
canUserPause,
|
|
71
85
|
canUserResume,
|
|
72
86
|
canUserStop,
|
|
87
|
+
isPremiseRecordingEnabled,
|
|
73
88
|
deriveRecordingStates,
|
|
74
89
|
extractLocusId,
|
|
75
90
|
};
|
package/src/webinar/index.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import {WebexPlugin} from '@webex/webex-core';
|
|
5
5
|
import {get} from 'lodash';
|
|
6
|
-
import {MEETINGS, SELF_ROLES} from '../constants';
|
|
6
|
+
import {HTTP_VERBS, MEETINGS, SELF_ROLES} from '../constants';
|
|
7
7
|
|
|
8
8
|
import WebinarCollection from './collection';
|
|
9
|
+
import LoggerProxy from '../common/logs/logger-proxy';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @class Webinar
|
|
@@ -22,6 +23,7 @@ const Webinar = WebexPlugin.extend({
|
|
|
22
23
|
canManageWebcast: 'boolean', // appears the ability to manage webcast
|
|
23
24
|
selfIsPanelist: 'boolean', // self is panelist
|
|
24
25
|
selfIsAttendee: 'boolean', // self is attendee
|
|
26
|
+
practiceSessionEnabled: 'boolean', // practice session enabled
|
|
25
27
|
},
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -59,18 +61,48 @@ const Webinar = WebexPlugin.extend({
|
|
|
59
61
|
* @returns {{isPromoted: boolean, isDemoted: boolean}} Role transition states
|
|
60
62
|
*/
|
|
61
63
|
updateRoleChanged(payload) {
|
|
64
|
+
const oldRoles = get(payload, 'oldRoles', []);
|
|
65
|
+
const newRoles = get(payload, 'newRoles', []);
|
|
66
|
+
|
|
62
67
|
const isPromoted =
|
|
63
|
-
|
|
64
|
-
get(payload, 'newRoles', []).includes(SELF_ROLES.PANELIST);
|
|
68
|
+
oldRoles.includes(SELF_ROLES.ATTENDEE) && newRoles.includes(SELF_ROLES.PANELIST);
|
|
65
69
|
const isDemoted =
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.set('
|
|
69
|
-
this.
|
|
70
|
-
this.updateCanManageWebcast(get(payload, 'newRoles', []).includes(SELF_ROLES.MODERATOR));
|
|
70
|
+
oldRoles.includes(SELF_ROLES.PANELIST) && newRoles.includes(SELF_ROLES.ATTENDEE);
|
|
71
|
+
this.set('selfIsPanelist', newRoles.includes(SELF_ROLES.PANELIST));
|
|
72
|
+
this.set('selfIsAttendee', newRoles.includes(SELF_ROLES.ATTENDEE));
|
|
73
|
+
this.updateCanManageWebcast(newRoles.includes(SELF_ROLES.MODERATOR));
|
|
71
74
|
|
|
72
75
|
return {isPromoted, isDemoted};
|
|
73
76
|
},
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* start or stop practice session for webinar
|
|
80
|
+
* @param {boolean} enabled
|
|
81
|
+
* @returns {Promise}
|
|
82
|
+
*/
|
|
83
|
+
setPracticeSessionState(enabled) {
|
|
84
|
+
return this.request({
|
|
85
|
+
method: HTTP_VERBS.PATCH,
|
|
86
|
+
uri: `${this.locusUrl}/controls`,
|
|
87
|
+
body: {
|
|
88
|
+
practiceSession: {
|
|
89
|
+
enabled,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
}).catch((error) => {
|
|
93
|
+
LoggerProxy.logger.error('Meeting:webinar#setPracticeSessionState failed', error);
|
|
94
|
+
throw error;
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* update practice session status
|
|
100
|
+
* @param {object} payload
|
|
101
|
+
* @returns {void}
|
|
102
|
+
*/
|
|
103
|
+
updatePracticeSessionStatus(payload) {
|
|
104
|
+
this.set('practiceSessionEnabled', payload.enabled);
|
|
105
|
+
},
|
|
74
106
|
});
|
|
75
107
|
|
|
76
108
|
export default Webinar;
|
|
@@ -33,6 +33,7 @@ describe('plugin-meetings', () => {
|
|
|
33
33
|
canStartManualCaption: null,
|
|
34
34
|
canStopManualCaption: null,
|
|
35
35
|
isManualCaptionActive: null,
|
|
36
|
+
isPremiseRecordingEnabled: null,
|
|
36
37
|
isSaveTranscriptsEnabled: null,
|
|
37
38
|
isWebexAssistantActive: null,
|
|
38
39
|
canViewCaptionPanel: null,
|
|
@@ -88,6 +89,11 @@ describe('plugin-meetings', () => {
|
|
|
88
89
|
canShowStageView: null,
|
|
89
90
|
canEnableStageView: null,
|
|
90
91
|
canDisableStageView: null,
|
|
92
|
+
isPracticeSessionOn : null,
|
|
93
|
+
isPracticeSessionOff : null,
|
|
94
|
+
canStartPracticeSession: null,
|
|
95
|
+
canStopPracticeSession: null,
|
|
96
|
+
|
|
91
97
|
...expected,
|
|
92
98
|
};
|
|
93
99
|
|
|
@@ -126,6 +132,7 @@ describe('plugin-meetings', () => {
|
|
|
126
132
|
'canStartManualCaption',
|
|
127
133
|
'canStopManualCaption',
|
|
128
134
|
'isManualCaptionActive',
|
|
135
|
+
'isPremiseRecordingEnabled',
|
|
129
136
|
'isSaveTranscriptsEnabled',
|
|
130
137
|
'isWebexAssistantActive',
|
|
131
138
|
'canViewCaptionPanel',
|
|
@@ -181,7 +188,12 @@ describe('plugin-meetings', () => {
|
|
|
181
188
|
'canShowStageView',
|
|
182
189
|
'canEnableStageView',
|
|
183
190
|
'canDisableStageView',
|
|
184
|
-
|
|
191
|
+
'isPracticeSessionOn',
|
|
192
|
+
'isPracticeSessionOff',
|
|
193
|
+
'canStartPracticeSession',
|
|
194
|
+
'canStopPracticeSession',
|
|
195
|
+
|
|
196
|
+
].forEach((key) => {
|
|
185
197
|
it(`get and set for ${key} work as expected`, () => {
|
|
186
198
|
const inMeetingActions = new InMeetingActions();
|
|
187
199
|
|
|
@@ -2465,6 +2465,61 @@ describe('plugin-meetings', () => {
|
|
|
2465
2465
|
checkWorking();
|
|
2466
2466
|
});
|
|
2467
2467
|
|
|
2468
|
+
it('should upload logs periodically', async () => {
|
|
2469
|
+
const clock = sinon.useFakeTimers();
|
|
2470
|
+
|
|
2471
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
2472
|
+
.stub()
|
|
2473
|
+
.resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
|
2474
|
+
|
|
2475
|
+
let logUploadCounter = 0;
|
|
2476
|
+
|
|
2477
|
+
TriggerProxy.trigger.callsFake((meetingObject, options, event) => {
|
|
2478
|
+
if (
|
|
2479
|
+
meetingObject === meeting &&
|
|
2480
|
+
options.file === 'meeting/index' &&
|
|
2481
|
+
options.function === 'uploadLogs' &&
|
|
2482
|
+
event === 'REQUEST_UPLOAD_LOGS'
|
|
2483
|
+
) {
|
|
2484
|
+
logUploadCounter += 1;
|
|
2485
|
+
}
|
|
2486
|
+
});
|
|
2487
|
+
|
|
2488
|
+
meeting.config.logUploadIntervalMultiplicationFactor = 1;
|
|
2489
|
+
meeting.meetingState = 'ACTIVE';
|
|
2490
|
+
|
|
2491
|
+
await meeting.addMedia({
|
|
2492
|
+
mediaSettings: {},
|
|
2493
|
+
});
|
|
2494
|
+
|
|
2495
|
+
const checkLogCounter = (delayInMinutes, expectedCounter) => {
|
|
2496
|
+
const delayInMilliseconds = delayInMinutes * 60 * 1000;
|
|
2497
|
+
|
|
2498
|
+
// first check that the counter is not increased just before the delay
|
|
2499
|
+
clock.tick(delayInMilliseconds - 50);
|
|
2500
|
+
assert.equal(logUploadCounter, expectedCounter - 1);
|
|
2501
|
+
|
|
2502
|
+
// and now check that it has reached expected value after the delay
|
|
2503
|
+
clock.tick(50);
|
|
2504
|
+
assert.equal(logUploadCounter, expectedCounter);
|
|
2505
|
+
};
|
|
2506
|
+
|
|
2507
|
+
checkLogCounter(0.1, 1);
|
|
2508
|
+
checkLogCounter(15, 2);
|
|
2509
|
+
checkLogCounter(30, 3);
|
|
2510
|
+
checkLogCounter(60, 4);
|
|
2511
|
+
checkLogCounter(60, 5);
|
|
2512
|
+
|
|
2513
|
+
// simulate media connection being removed -> 1 more upload should happen, but nothing more afterwards
|
|
2514
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
2515
|
+
checkLogCounter(60, 6);
|
|
2516
|
+
|
|
2517
|
+
clock.tick(120*1000*60);
|
|
2518
|
+
assert.equal(logUploadCounter, 6);
|
|
2519
|
+
|
|
2520
|
+
clock.restore();
|
|
2521
|
+
});
|
|
2522
|
+
|
|
2468
2523
|
it('should attach the media and return promise when in the lobby if allowMediaInLobby is set', async () => {
|
|
2469
2524
|
meeting.roap.doTurnDiscovery = sinon
|
|
2470
2525
|
.stub()
|
|
@@ -8620,6 +8675,13 @@ describe('plugin-meetings', () => {
|
|
|
8620
8675
|
{payload: test1}
|
|
8621
8676
|
);
|
|
8622
8677
|
assert.calledOnce(meeting.updateLLMConnection);
|
|
8678
|
+
assert.calledOnceWithExactly(
|
|
8679
|
+
Metrics.sendBehavioralMetric,
|
|
8680
|
+
BEHAVIORAL_METRICS.GUEST_ENTERED_LOBBY,
|
|
8681
|
+
{
|
|
8682
|
+
correlation_id: meeting.correlationId,
|
|
8683
|
+
}
|
|
8684
|
+
);
|
|
8623
8685
|
done();
|
|
8624
8686
|
});
|
|
8625
8687
|
it('listens to the self admitted guest event', (done) => {
|
|
@@ -8641,6 +8703,13 @@ describe('plugin-meetings', () => {
|
|
|
8641
8703
|
assert.calledOnce(meeting.updateLLMConnection);
|
|
8642
8704
|
assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
|
|
8643
8705
|
|
|
8706
|
+
assert.calledOnceWithExactly(
|
|
8707
|
+
Metrics.sendBehavioralMetric,
|
|
8708
|
+
BEHAVIORAL_METRICS.GUEST_EXITED_LOBBY,
|
|
8709
|
+
{
|
|
8710
|
+
correlation_id: meeting.correlationId,
|
|
8711
|
+
}
|
|
8712
|
+
);
|
|
8644
8713
|
done();
|
|
8645
8714
|
});
|
|
8646
8715
|
|
|
@@ -8973,6 +9042,8 @@ describe('plugin-meetings', () => {
|
|
|
8973
9042
|
});
|
|
8974
9043
|
|
|
8975
9044
|
it('listens to MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED', async () => {
|
|
9045
|
+
meeting.webinar.updatePracticeSessionStatus = sinon.stub();
|
|
9046
|
+
|
|
8976
9047
|
const state = {example: 'value'};
|
|
8977
9048
|
|
|
8978
9049
|
await meeting.locusInfo.emitScoped(
|
|
@@ -8981,6 +9052,7 @@ describe('plugin-meetings', () => {
|
|
|
8981
9052
|
{state}
|
|
8982
9053
|
);
|
|
8983
9054
|
|
|
9055
|
+
assert.calledOnceWithExactly( meeting.webinar.updatePracticeSessionStatus, state);
|
|
8984
9056
|
assert.calledWith(
|
|
8985
9057
|
TriggerProxy.trigger,
|
|
8986
9058
|
meeting,
|
|
@@ -45,6 +45,7 @@ describe('plugin-meetings', () => {
|
|
|
45
45
|
meeting.cleanupLocalStreams = sinon.stub().returns(Promise.resolve());
|
|
46
46
|
meeting.closeRemoteStreams = sinon.stub().returns(Promise.resolve());
|
|
47
47
|
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
48
|
+
meeting.stopPeriodicLogUpload = sinon.stub();
|
|
48
49
|
|
|
49
50
|
meeting.unsetRemoteStreams = sinon.stub();
|
|
50
51
|
meeting.unsetPeerConnections = sinon.stub();
|
|
@@ -70,6 +71,7 @@ describe('plugin-meetings', () => {
|
|
|
70
71
|
assert.calledOnce(meeting.cleanupLocalStreams);
|
|
71
72
|
assert.calledOnce(meeting.closeRemoteStreams);
|
|
72
73
|
assert.calledOnce(meeting.closePeerConnections);
|
|
74
|
+
assert.calledOnce(meeting.stopPeriodicLogUpload);
|
|
73
75
|
|
|
74
76
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
|
75
77
|
assert.calledOnce(meeting.unsetPeerConnections);
|
|
@@ -131,9 +131,9 @@ describe('plugin-meetings', () => {
|
|
|
131
131
|
logger,
|
|
132
132
|
people: {
|
|
133
133
|
_getMe: sinon.stub().resolves({
|
|
134
|
-
type: 'validuser',
|
|
134
|
+
type: 'validuser',
|
|
135
135
|
}),
|
|
136
|
-
}
|
|
136
|
+
},
|
|
137
137
|
});
|
|
138
138
|
|
|
139
139
|
startReachabilityStub = sinon.stub(webex.meetings, 'startReachability').resolves();
|
|
@@ -1985,6 +1985,8 @@ describe('plugin-meetings', () => {
|
|
|
1985
1985
|
const meetingIds = {
|
|
1986
1986
|
meetingId: meeting.id,
|
|
1987
1987
|
correlationId: meeting.correlationId,
|
|
1988
|
+
roles: meeting.roles,
|
|
1989
|
+
callStateForMetrics: meeting.callStateForMetrics,
|
|
1988
1990
|
};
|
|
1989
1991
|
|
|
1990
1992
|
webex.meetings.destroy(meeting, test1);
|
|
@@ -2021,6 +2023,8 @@ describe('plugin-meetings', () => {
|
|
|
2021
2023
|
|
|
2022
2024
|
assert.equal(deletedMeetingInfo.id, meetingIds.meetingId);
|
|
2023
2025
|
assert.equal(deletedMeetingInfo.correlationId, meetingIds.correlationId);
|
|
2026
|
+
assert.equal(deletedMeetingInfo.roles, meetingIds.roles);
|
|
2027
|
+
assert.equal(deletedMeetingInfo.callStateForMetrics, meetingIds.callStateForMetrics);
|
|
2024
2028
|
});
|
|
2025
2029
|
});
|
|
2026
2030
|
|
|
@@ -2092,7 +2096,7 @@ describe('plugin-meetings', () => {
|
|
|
2092
2096
|
);
|
|
2093
2097
|
});
|
|
2094
2098
|
|
|
2095
|
-
const setup = ({me = {
|
|
2099
|
+
const setup = ({me = {type: 'validuser'}, user} = {}) => {
|
|
2096
2100
|
loggerProxySpy = sinon.spy(LoggerProxy.logger, 'error');
|
|
2097
2101
|
assert.deepEqual(webex.internal.services._getCatalog().getAllowedDomains(), []);
|
|
2098
2102
|
|
|
@@ -2113,9 +2117,9 @@ describe('plugin-meetings', () => {
|
|
|
2113
2117
|
|
|
2114
2118
|
it('should not call request.getMeetingPreferences if user is a guest', async () => {
|
|
2115
2119
|
setup({me: {type: 'appuser'}});
|
|
2116
|
-
|
|
2120
|
+
|
|
2117
2121
|
await webex.meetings.fetchUserPreferredWebexSite();
|
|
2118
|
-
|
|
2122
|
+
|
|
2119
2123
|
assert.equal(webex.meetings.preferredWebexSite, '');
|
|
2120
2124
|
assert.deepEqual(webex.internal.services._getCatalog().getAllowedDomains(), []);
|
|
2121
2125
|
assert.notCalled(webex.internal.services.getMeetingPreferences);
|
|
@@ -262,5 +262,100 @@ describe('plugin-meetings', () => {
|
|
|
262
262
|
testParams(false);
|
|
263
263
|
});
|
|
264
264
|
});
|
|
265
|
+
|
|
266
|
+
describe('#getAddMemberBody', () => {
|
|
267
|
+
it('returns the correct body with email address and roles', () => {
|
|
268
|
+
const options = {
|
|
269
|
+
invitee: {
|
|
270
|
+
emailAddress: 'test@example.com',
|
|
271
|
+
roles: ['role1', 'role2'],
|
|
272
|
+
},
|
|
273
|
+
alertIfActive: true,
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
assert.deepEqual(MembersUtil.getAddMemberBody(options), {
|
|
277
|
+
invitees: [
|
|
278
|
+
{
|
|
279
|
+
address: 'test@example.com',
|
|
280
|
+
roles: ['role1', 'role2'],
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
alertIfActive: true,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('returns the correct body with phone number and no roles', () => {
|
|
288
|
+
const options = {
|
|
289
|
+
invitee: {
|
|
290
|
+
phoneNumber: '1234567890',
|
|
291
|
+
},
|
|
292
|
+
alertIfActive: false,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
assert.deepEqual(MembersUtil.getAddMemberBody(options), {
|
|
296
|
+
invitees: [
|
|
297
|
+
{
|
|
298
|
+
address: '1234567890',
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
alertIfActive: false,
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('returns the correct body with fallback to email', () => {
|
|
306
|
+
const options = {
|
|
307
|
+
invitee: {
|
|
308
|
+
email: 'fallback@example.com',
|
|
309
|
+
},
|
|
310
|
+
alertIfActive: true,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
assert.deepEqual(MembersUtil.getAddMemberBody(options), {
|
|
314
|
+
invitees: [
|
|
315
|
+
{
|
|
316
|
+
address: 'fallback@example.com',
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
alertIfActive: true,
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('handles missing `alertIfActive` gracefully', () => {
|
|
324
|
+
const options = {
|
|
325
|
+
invitee: {
|
|
326
|
+
emailAddress: 'test@example.com',
|
|
327
|
+
roles: ['role1'],
|
|
328
|
+
},
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
assert.deepEqual(MembersUtil.getAddMemberBody(options), {
|
|
332
|
+
invitees: [
|
|
333
|
+
{
|
|
334
|
+
address: 'test@example.com',
|
|
335
|
+
roles: ['role1'],
|
|
336
|
+
},
|
|
337
|
+
],
|
|
338
|
+
alertIfActive: undefined,
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('ignores roles if not provided', () => {
|
|
343
|
+
const options = {
|
|
344
|
+
invitee: {
|
|
345
|
+
emailAddress: 'test@example.com',
|
|
346
|
+
},
|
|
347
|
+
alertIfActive: false,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
assert.deepEqual(MembersUtil.getAddMemberBody(options), {
|
|
351
|
+
invitees: [
|
|
352
|
+
{
|
|
353
|
+
address: 'test@example.com',
|
|
354
|
+
},
|
|
355
|
+
],
|
|
356
|
+
alertIfActive: false,
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|
|
265
360
|
});
|
|
266
361
|
});
|
|
@@ -15,6 +15,7 @@ describe('ClusterReachability', () => {
|
|
|
15
15
|
let previousRTCPeerConnection;
|
|
16
16
|
let clusterReachability;
|
|
17
17
|
let fakePeerConnection;
|
|
18
|
+
let gatherIceCandidatesSpy;
|
|
18
19
|
|
|
19
20
|
const emittedEvents: Record<Events, (ResultEventData | ClientMediaIpsUpdatedEventData)[]> = {
|
|
20
21
|
[Events.resultReady]: [],
|
|
@@ -44,6 +45,8 @@ describe('ClusterReachability', () => {
|
|
|
44
45
|
xtls: ['stun:xtls1.webex.com', 'stun:xtls2.webex.com:443'],
|
|
45
46
|
});
|
|
46
47
|
|
|
48
|
+
gatherIceCandidatesSpy = sinon.spy(clusterReachability, 'gatherIceCandidates');
|
|
49
|
+
|
|
47
50
|
resetEmittedEvents();
|
|
48
51
|
|
|
49
52
|
clusterReachability.on(Events.resultReady, (data: ResultEventData) => {
|
|
@@ -151,6 +154,10 @@ describe('ClusterReachability', () => {
|
|
|
151
154
|
assert.calledOnceWithExactly(fakePeerConnection.createOffer, {offerToReceiveAudio: true});
|
|
152
155
|
assert.calledOnce(fakePeerConnection.setLocalDescription);
|
|
153
156
|
|
|
157
|
+
// Make sure that gatherIceCandidates is called before setLocalDescription
|
|
158
|
+
// as setLocalDescription triggers the ICE gathering process
|
|
159
|
+
assert.isTrue(gatherIceCandidatesSpy.calledBefore(fakePeerConnection.setLocalDescription));
|
|
160
|
+
|
|
154
161
|
clusterReachability.abort();
|
|
155
162
|
await promise;
|
|
156
163
|
|