@webex/plugin-meetings 3.7.0-next.1 → 3.7.0-next.10
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 +286 -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 +93 -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 +74 -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,63 @@ 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 = (delay, expectedCounter) => {
|
|
2496
|
+
// first check that the counter is not increased just before the delay
|
|
2497
|
+
clock.tick(delay - 50);
|
|
2498
|
+
assert.equal(logUploadCounter, expectedCounter - 1);
|
|
2499
|
+
|
|
2500
|
+
// and now check that it has reached expected value after the delay
|
|
2501
|
+
clock.tick(50);
|
|
2502
|
+
assert.equal(logUploadCounter, expectedCounter);
|
|
2503
|
+
};
|
|
2504
|
+
|
|
2505
|
+
checkLogCounter(100, 1);
|
|
2506
|
+
checkLogCounter(1000, 2);
|
|
2507
|
+
checkLogCounter(15000, 3);
|
|
2508
|
+
checkLogCounter(15000, 4);
|
|
2509
|
+
checkLogCounter(30000, 5);
|
|
2510
|
+
checkLogCounter(30000, 6);
|
|
2511
|
+
checkLogCounter(30000, 7);
|
|
2512
|
+
checkLogCounter(60000, 8);
|
|
2513
|
+
checkLogCounter(60000, 9);
|
|
2514
|
+
checkLogCounter(60000, 10);
|
|
2515
|
+
|
|
2516
|
+
// simulate media connection being removed -> no more log uploads should happen
|
|
2517
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
2518
|
+
|
|
2519
|
+
clock.tick(60000);
|
|
2520
|
+
assert.equal(logUploadCounter, 11);
|
|
2521
|
+
|
|
2522
|
+
clock.restore();
|
|
2523
|
+
});
|
|
2524
|
+
|
|
2468
2525
|
it('should attach the media and return promise when in the lobby if allowMediaInLobby is set', async () => {
|
|
2469
2526
|
meeting.roap.doTurnDiscovery = sinon
|
|
2470
2527
|
.stub()
|
|
@@ -8620,6 +8677,13 @@ describe('plugin-meetings', () => {
|
|
|
8620
8677
|
{payload: test1}
|
|
8621
8678
|
);
|
|
8622
8679
|
assert.calledOnce(meeting.updateLLMConnection);
|
|
8680
|
+
assert.calledOnceWithExactly(
|
|
8681
|
+
Metrics.sendBehavioralMetric,
|
|
8682
|
+
BEHAVIORAL_METRICS.GUEST_ENTERED_LOBBY,
|
|
8683
|
+
{
|
|
8684
|
+
correlation_id: meeting.correlationId,
|
|
8685
|
+
}
|
|
8686
|
+
);
|
|
8623
8687
|
done();
|
|
8624
8688
|
});
|
|
8625
8689
|
it('listens to the self admitted guest event', (done) => {
|
|
@@ -8641,6 +8705,13 @@ describe('plugin-meetings', () => {
|
|
|
8641
8705
|
assert.calledOnce(meeting.updateLLMConnection);
|
|
8642
8706
|
assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
|
|
8643
8707
|
|
|
8708
|
+
assert.calledOnceWithExactly(
|
|
8709
|
+
Metrics.sendBehavioralMetric,
|
|
8710
|
+
BEHAVIORAL_METRICS.GUEST_EXITED_LOBBY,
|
|
8711
|
+
{
|
|
8712
|
+
correlation_id: meeting.correlationId,
|
|
8713
|
+
}
|
|
8714
|
+
);
|
|
8644
8715
|
done();
|
|
8645
8716
|
});
|
|
8646
8717
|
|
|
@@ -8973,6 +9044,8 @@ describe('plugin-meetings', () => {
|
|
|
8973
9044
|
});
|
|
8974
9045
|
|
|
8975
9046
|
it('listens to MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED', async () => {
|
|
9047
|
+
meeting.webinar.updatePracticeSessionStatus = sinon.stub();
|
|
9048
|
+
|
|
8976
9049
|
const state = {example: 'value'};
|
|
8977
9050
|
|
|
8978
9051
|
await meeting.locusInfo.emitScoped(
|
|
@@ -8981,6 +9054,7 @@ describe('plugin-meetings', () => {
|
|
|
8981
9054
|
{state}
|
|
8982
9055
|
);
|
|
8983
9056
|
|
|
9057
|
+
assert.calledOnceWithExactly( meeting.webinar.updatePracticeSessionStatus, state);
|
|
8984
9058
|
assert.calledWith(
|
|
8985
9059
|
TriggerProxy.trigger,
|
|
8986
9060
|
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
|
|