@webex/plugin-meetings 3.12.0-next.44 → 3.12.0-next.46
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/aiEnableRequest/index.js +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/controls-options-manager/index.js +17 -5
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/interpretation/index.js +10 -1
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +94 -20
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +15 -2
- package/dist/meeting/util.js.map +1 -1
- package/dist/recording-controller/index.js +1 -3
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/types/controls-options-manager/index.d.ts +10 -0
- package/dist/types/meeting/index.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +7 -0
- package/dist/webinar/index.js +68 -17
- package/dist/webinar/index.js.map +1 -1
- package/package.json +3 -3
- package/src/controls-options-manager/index.ts +22 -6
- package/src/interpretation/index.ts +25 -8
- package/src/meeting/index.ts +79 -6
- package/src/meeting/util.ts +16 -2
- package/src/recording-controller/index.ts +1 -2
- package/src/webinar/index.ts +88 -21
- package/test/unit/spec/controls-options-manager/index.js +35 -32
- package/test/unit/spec/interpretation/index.ts +26 -4
- package/test/unit/spec/meeting/index.js +183 -0
- package/test/unit/spec/meeting/muteState.js +3 -0
- package/test/unit/spec/meeting/utils.js +25 -0
- package/test/unit/spec/recording-controller/index.js +9 -8
- package/test/unit/spec/webinar/index.ts +81 -16
package/src/meeting/index.ts
CHANGED
|
@@ -6407,6 +6407,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6407
6407
|
|
|
6408
6408
|
/**
|
|
6409
6409
|
* Disconnects and cleans up the default LLM session listeners/timers.
|
|
6410
|
+
*
|
|
6411
|
+
* Ownership-aware: only calls `disconnectLLM` when this meeting is the
|
|
6412
|
+
* current owner of the default LLM session (or when no owner is recorded).
|
|
6413
|
+
* Event listeners belonging to this meeting instance are always detached
|
|
6414
|
+
* so they do not receive another meeting's relay events.
|
|
6415
|
+
*
|
|
6410
6416
|
* @param {Object} options
|
|
6411
6417
|
* @param {boolean} [options.removeOnlineListener=true] removes the one-time online listener
|
|
6412
6418
|
* @param {boolean} [options.throwOnError=true] rethrows disconnect errors when true
|
|
@@ -6419,12 +6425,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6419
6425
|
removeOnlineListener?: boolean;
|
|
6420
6426
|
throwOnError?: boolean;
|
|
6421
6427
|
} = {}): Promise<void> => {
|
|
6428
|
+
// @ts-ignore - Fix type
|
|
6429
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
6430
|
+
const isOwner = !currentOwner || currentOwner === this.id;
|
|
6431
|
+
|
|
6422
6432
|
try {
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6433
|
+
if (isOwner) {
|
|
6434
|
+
// @ts-ignore - Fix type
|
|
6435
|
+
await this.webex.internal.llm.disconnectLLM({
|
|
6436
|
+
code: 3050,
|
|
6437
|
+
reason: 'done (permanent)',
|
|
6438
|
+
});
|
|
6439
|
+
} else {
|
|
6440
|
+
LoggerProxy.logger.info(
|
|
6441
|
+
`Meeting:index#cleanupLLMConneciton --> skipping disconnect; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6442
|
+
);
|
|
6443
|
+
}
|
|
6428
6444
|
} catch (error) {
|
|
6429
6445
|
LoggerProxy.logger.error(
|
|
6430
6446
|
'Meeting:index#cleanupLLMConneciton --> Failed to disconnect default LLM session',
|
|
@@ -6440,6 +6456,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6440
6456
|
this.webex.internal.llm.off('online', this.handleLLMOnline);
|
|
6441
6457
|
}
|
|
6442
6458
|
this.stopListeningForLLMEvents();
|
|
6459
|
+
|
|
6460
|
+
// If this meeting owned (or could have owned) the default LLM session,
|
|
6461
|
+
// always release the owner tag here regardless of whether disconnectLLM
|
|
6462
|
+
// resolved. `disconnectLLM` only clears the owner on its success path,
|
|
6463
|
+
// so a failed disconnect would otherwise leave a stale owner pointing
|
|
6464
|
+
// at a torn-down meeting and permanently block other meetings'
|
|
6465
|
+
// `updateLLMConnection` calls via the ownership guard.
|
|
6466
|
+
if (isOwner) {
|
|
6467
|
+
// @ts-ignore - Fix type
|
|
6468
|
+
this.webex.internal.llm.setOwnerMeetingId?.(undefined);
|
|
6469
|
+
}
|
|
6443
6470
|
}
|
|
6444
6471
|
};
|
|
6445
6472
|
|
|
@@ -6541,8 +6568,33 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6541
6568
|
|
|
6542
6569
|
const dataChannelUrl = datachannelUrl;
|
|
6543
6570
|
|
|
6571
|
+
// Ownership guard: when the default LLM session is already connected and
|
|
6572
|
+
// owned by a *different* Meeting instance, do not disconnect or reconfigure
|
|
6573
|
+
// it. Another meeting's `updateLLMConnection` must be ignored here to
|
|
6574
|
+
// avoid killing the socket it relies on. We only proceed to manage the
|
|
6575
|
+
// connection when this meeting is the current owner, or when no owner is
|
|
6576
|
+
// set yet (first claim).
|
|
6577
|
+
// @ts-ignore - Fix type
|
|
6578
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
6579
|
+
|
|
6544
6580
|
// @ts-ignore - Fix type
|
|
6545
6581
|
if (this.webex.internal.llm.isConnected()) {
|
|
6582
|
+
if (currentOwner && currentOwner !== this.id) {
|
|
6583
|
+
// Another meeting owns the live LLM socket. We must not disconnect
|
|
6584
|
+
// or reconfigure it -- doing so would tear down a session the
|
|
6585
|
+
// owning meeting still relies on. Locus/datachannel URL mismatch is
|
|
6586
|
+
// expected here (each meeting has its own locus URL) and is NOT a
|
|
6587
|
+
// valid signal of staleness, so we never reclaim from this path.
|
|
6588
|
+
// The only safe reclaim mechanism is the `finally`-block owner-tag
|
|
6589
|
+
// release in `cleanupLLMConneciton`, which fires when this meeting
|
|
6590
|
+
// itself is being torn down.
|
|
6591
|
+
LoggerProxy.logger.info(
|
|
6592
|
+
`Meeting:index#updateLLMConnection --> skipping; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
6593
|
+
);
|
|
6594
|
+
|
|
6595
|
+
return undefined;
|
|
6596
|
+
}
|
|
6597
|
+
|
|
6546
6598
|
if (
|
|
6547
6599
|
// @ts-ignore - Fix type
|
|
6548
6600
|
url === this.webex.internal.llm.getLocusUrl() &&
|
|
@@ -6563,6 +6615,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6563
6615
|
return this.webex.internal.llm
|
|
6564
6616
|
.registerAndConnect(url, dataChannelUrl, datachannelToken)
|
|
6565
6617
|
.then((registerAndConnectResult) => {
|
|
6618
|
+
// Record ownership of the default LLM session for this meeting so
|
|
6619
|
+
// subsequent cross-meeting `updateLLMConnection` / `cleanupLLMConneciton`
|
|
6620
|
+
// calls can detect and skip work that doesn't belong to them.
|
|
6621
|
+
// @ts-ignore - Fix type
|
|
6622
|
+
this.webex.internal.llm.setOwnerMeetingId?.(this.id);
|
|
6566
6623
|
// @ts-ignore - Fix type
|
|
6567
6624
|
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
6568
6625
|
// @ts-ignore - Fix type
|
|
@@ -9857,7 +9914,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
9857
9914
|
// received mid-teardown do not trigger Locus syncs. Calling it here
|
|
9858
9915
|
// again would double-emit MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
9859
9916
|
// because stopTranscription() always fires its trigger.
|
|
9860
|
-
|
|
9917
|
+
//
|
|
9918
|
+
// Ownership-aware token clear: only clear the shared LLM data channel
|
|
9919
|
+
// tokens when this meeting owns (or no meeting owns) the default LLM
|
|
9920
|
+
// session. Otherwise we would wipe tokens still in use by another
|
|
9921
|
+
// meeting's active LLM connection.
|
|
9922
|
+
// @ts-ignore - Fix type
|
|
9923
|
+
const currentOwner = this.webex.internal.llm.getOwnerMeetingId();
|
|
9924
|
+
const isOwner = !currentOwner || currentOwner === this.id;
|
|
9925
|
+
|
|
9926
|
+
if (isOwner) {
|
|
9927
|
+
this.clearDataChannelToken();
|
|
9928
|
+
} else {
|
|
9929
|
+
LoggerProxy.logger.info(
|
|
9930
|
+
`Meeting:index#clearMeetingData --> skipping clearDataChannelToken; LLM owned by meeting ${currentOwner}, not ${this.id}`
|
|
9931
|
+
);
|
|
9932
|
+
}
|
|
9933
|
+
|
|
9861
9934
|
await this.cleanupLLMConneciton({throwOnError: false});
|
|
9862
9935
|
};
|
|
9863
9936
|
|
package/src/meeting/util.ts
CHANGED
|
@@ -846,6 +846,19 @@ const MeetingUtil = {
|
|
|
846
846
|
requestBody.sequence = sequence;
|
|
847
847
|
},
|
|
848
848
|
|
|
849
|
+
/**
|
|
850
|
+
* Checks if Locus API response contains a Locus DTO
|
|
851
|
+
*
|
|
852
|
+
* @param {any} response http response from Locus API call
|
|
853
|
+
* @returns {boolean} true if response contains a Locus DTO
|
|
854
|
+
*/
|
|
855
|
+
isLocusDtoInAPIResponse(response: any) {
|
|
856
|
+
return (
|
|
857
|
+
response?.body?.locus || // for APIs called on our participant - locus is one of props in the response body
|
|
858
|
+
response?.body?.url // for APIs that act on locus itself (like mute all), the body is the locus
|
|
859
|
+
);
|
|
860
|
+
},
|
|
861
|
+
|
|
849
862
|
/**
|
|
850
863
|
* Updates the locus info for the meeting with the locus
|
|
851
864
|
* information returned from API requests made to Locus
|
|
@@ -854,12 +867,13 @@ const MeetingUtil = {
|
|
|
854
867
|
* @param {Object} response The response of the http request
|
|
855
868
|
* @returns {Object}
|
|
856
869
|
*/
|
|
857
|
-
updateLocusFromApiResponse: (meeting, response) => {
|
|
870
|
+
updateLocusFromApiResponse: (meeting: any, response: any) => {
|
|
858
871
|
if (!meeting) {
|
|
859
872
|
return response;
|
|
860
873
|
}
|
|
861
874
|
|
|
862
|
-
|
|
875
|
+
// locus API responses can come in different shapes:
|
|
876
|
+
if (MeetingUtil.isLocusDtoInAPIResponse(response)) {
|
|
863
877
|
meeting.locusInfo.handleLocusAPIResponse(meeting, response.body);
|
|
864
878
|
}
|
|
865
879
|
|
|
@@ -261,8 +261,7 @@ export default class RecordingController {
|
|
|
261
261
|
|
|
262
262
|
LoggerProxy.logger.log(`RecordingController:index#recordingControls --> ${record}`);
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
return this.request.request({
|
|
264
|
+
return this.request.locusDeltaRequest({
|
|
266
265
|
uri: `${this.locusUrl}/${CONTROLS}`,
|
|
267
266
|
body: {
|
|
268
267
|
record,
|
package/src/webinar/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
|
|
19
19
|
import WebinarCollection from './collection';
|
|
20
20
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
21
|
+
import MeetingUtil from '../meeting/util';
|
|
21
22
|
import {sanitizeParams} from './utils';
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -98,13 +99,49 @@ const Webinar = WebexPlugin.extend({
|
|
|
98
99
|
return {isPromoted, isDemoted};
|
|
99
100
|
},
|
|
100
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Resolves the meeting associated with this webinar instance, guarded against the
|
|
104
|
+
* meetingId pointer drifting onto an unrelated transient meeting (e.g. an inbound
|
|
105
|
+
* 1:1 call) that may exist in the meeting collection. Returns the meeting only when
|
|
106
|
+
* its locusUrl matches this webinar's tracked locusUrl. Returns undefined (with a
|
|
107
|
+
* warning) when the meeting cannot be resolved or when the webinar's locusUrl has
|
|
108
|
+
* not been initialized yet — callers must treat this as "no owned meeting" rather
|
|
109
|
+
* than fall through to an unvalidated lookup.
|
|
110
|
+
* @returns {object|undefined}
|
|
111
|
+
*/
|
|
112
|
+
getValidatedWebinarMeeting() {
|
|
113
|
+
const meeting = this.webex.meetings.getMeetingByType(_ID_, this.meetingId);
|
|
114
|
+
|
|
115
|
+
if (!meeting) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!this.locusUrl) {
|
|
120
|
+
LoggerProxy.logger.warn(
|
|
121
|
+
`Webinar:index#getValidatedWebinarMeeting --> skipping; webinar locusUrl is not yet initialized for meetingId ${this.meetingId}`
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (meeting.locusUrl !== this.locusUrl) {
|
|
128
|
+
LoggerProxy.logger.warn(
|
|
129
|
+
`Webinar:index#getValidatedWebinarMeeting --> skipping; meeting ${this.meetingId} locusUrl ${meeting.locusUrl} does not match webinar locusUrl ${this.locusUrl}`
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return meeting;
|
|
136
|
+
},
|
|
137
|
+
|
|
101
138
|
/**
|
|
102
139
|
* should join practice session data channel or not
|
|
103
140
|
* @param {Object} {isPromoted: boolean, isDemoted: boolean}} Role transition states
|
|
104
141
|
* @returns {void}
|
|
105
142
|
*/
|
|
106
143
|
updateStatusByRole({isPromoted, isDemoted}) {
|
|
107
|
-
const meeting = this.
|
|
144
|
+
const meeting = this.getValidatedWebinarMeeting();
|
|
108
145
|
|
|
109
146
|
if (
|
|
110
147
|
(isDemoted && meeting?.shareStatus === SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE) ||
|
|
@@ -128,6 +165,9 @@ const Webinar = WebexPlugin.extend({
|
|
|
128
165
|
|
|
129
166
|
/**
|
|
130
167
|
* Disconnects the practice session data channel and removes its relay listener.
|
|
168
|
+
* The listener reference removed here is the exact callback captured at subscribe
|
|
169
|
+
* time (see updatePSDataChannel) so that cleanup is correct even if the underlying
|
|
170
|
+
* meeting can no longer be resolved (e.g. locusUrl mismatch).
|
|
131
171
|
* @returns {Promise<void>}
|
|
132
172
|
*/
|
|
133
173
|
async cleanupPSDataChannel() {
|
|
@@ -137,8 +177,6 @@ const Webinar = WebexPlugin.extend({
|
|
|
137
177
|
this._pendingOnlineListener = null;
|
|
138
178
|
}
|
|
139
179
|
|
|
140
|
-
const meeting = this.webex.meetings.getMeetingByType(_ID_, this.meetingId);
|
|
141
|
-
|
|
142
180
|
// @ts-ignore - Fix type
|
|
143
181
|
await this.webex.internal.llm.disconnectLLM(
|
|
144
182
|
{
|
|
@@ -147,15 +185,21 @@ const Webinar = WebexPlugin.extend({
|
|
|
147
185
|
},
|
|
148
186
|
LLM_PRACTICE_SESSION
|
|
149
187
|
);
|
|
150
|
-
|
|
151
|
-
this.
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
188
|
+
|
|
189
|
+
if (this._practiceSessionRelayListener) {
|
|
190
|
+
// @ts-ignore - Fix type
|
|
191
|
+
this.webex.internal.llm.off(
|
|
192
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
193
|
+
this._practiceSessionRelayListener
|
|
194
|
+
);
|
|
195
|
+
this._practiceSessionRelayListener = null;
|
|
196
|
+
}
|
|
155
197
|
},
|
|
156
198
|
|
|
157
199
|
/**
|
|
158
200
|
* Ensures practice-session token exists before registering the practice LLM channel.
|
|
201
|
+
* Caller is responsible for passing a meeting that has already been resolved via
|
|
202
|
+
* getValidatedWebinarMeeting() — this method does not re-validate ownership.
|
|
159
203
|
* @param {object} meeting
|
|
160
204
|
* @returns {Promise<string|undefined>}
|
|
161
205
|
*/
|
|
@@ -211,7 +255,7 @@ const Webinar = WebexPlugin.extend({
|
|
|
211
255
|
this._updatePSDataChannelSequence = (this._updatePSDataChannelSequence || 0) + 1;
|
|
212
256
|
const invocationSequence = this._updatePSDataChannelSequence;
|
|
213
257
|
|
|
214
|
-
const meeting = this.
|
|
258
|
+
const meeting = this.getValidatedWebinarMeeting();
|
|
215
259
|
const isPracticeSession = meeting?.isJoined() && this.isJoinPracticeSessionDataChannel();
|
|
216
260
|
|
|
217
261
|
if (!isPracticeSession) {
|
|
@@ -312,15 +356,21 @@ const Webinar = WebexPlugin.extend({
|
|
|
312
356
|
LLM_PRACTICE_SESSION
|
|
313
357
|
)
|
|
314
358
|
.then((registerAndConnectResult) => {
|
|
315
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
359
|
+
// Track the exact listener reference so cleanupPSDataChannel can
|
|
360
|
+
// unsubscribe deterministically, even if the meeting can no longer
|
|
361
|
+
// be resolved at cleanup time.
|
|
362
|
+
if (this._practiceSessionRelayListener) {
|
|
363
|
+
// @ts-ignore - Fix type
|
|
364
|
+
this.webex.internal.llm.off(
|
|
365
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
366
|
+
this._practiceSessionRelayListener
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
this._practiceSessionRelayListener = meeting?.processRelayEvent;
|
|
320
370
|
// @ts-ignore - Fix type
|
|
321
371
|
this.webex.internal.llm.on(
|
|
322
372
|
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
323
|
-
|
|
373
|
+
this._practiceSessionRelayListener
|
|
324
374
|
);
|
|
325
375
|
// @ts-ignore - Fix type
|
|
326
376
|
this.webex.internal.voicea?.announce?.();
|
|
@@ -341,6 +391,8 @@ const Webinar = WebexPlugin.extend({
|
|
|
341
391
|
* @returns {Promise}
|
|
342
392
|
*/
|
|
343
393
|
setPracticeSessionState(enabled) {
|
|
394
|
+
const meeting = this.getValidatedWebinarMeeting();
|
|
395
|
+
|
|
344
396
|
return this.request({
|
|
345
397
|
method: HTTP_VERBS.PATCH,
|
|
346
398
|
uri: `${this.locusUrl}/controls`,
|
|
@@ -349,10 +401,16 @@ const Webinar = WebexPlugin.extend({
|
|
|
349
401
|
enabled,
|
|
350
402
|
},
|
|
351
403
|
},
|
|
352
|
-
})
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
404
|
+
})
|
|
405
|
+
.then((response) => {
|
|
406
|
+
MeetingUtil.updateLocusFromApiResponse(meeting, response);
|
|
407
|
+
|
|
408
|
+
return response;
|
|
409
|
+
})
|
|
410
|
+
.catch((error) => {
|
|
411
|
+
LoggerProxy.logger.error('Meeting:webinar#setPracticeSessionState failed', error);
|
|
412
|
+
throw error;
|
|
413
|
+
});
|
|
356
414
|
},
|
|
357
415
|
|
|
358
416
|
/**
|
|
@@ -532,7 +590,14 @@ const Webinar = WebexPlugin.extend({
|
|
|
532
590
|
* @returns {Promise}
|
|
533
591
|
*/
|
|
534
592
|
async searchLargeScaleWebinarAttendees(payload) {
|
|
535
|
-
const meeting = this.
|
|
593
|
+
const meeting = this.getValidatedWebinarMeeting();
|
|
594
|
+
if (!meeting) {
|
|
595
|
+
LoggerProxy.logger.error(
|
|
596
|
+
'Meeting:webinar5k#searchLargeScaleWebinarAttendees failed --> webinar meeting could not be validated'
|
|
597
|
+
);
|
|
598
|
+
throw new Error('Meeting:webinar5k#Webinar meeting is not resolvable for the current locus');
|
|
599
|
+
}
|
|
600
|
+
|
|
536
601
|
const rawParams = {
|
|
537
602
|
search_text: payload?.queryString,
|
|
538
603
|
limit: payload?.limit ?? DEFAULT_LARGE_SCALE_WEBINAR_ATTENDEE_SEARCH_LIMIT,
|
|
@@ -540,7 +605,9 @@ const Webinar = WebexPlugin.extend({
|
|
|
540
605
|
};
|
|
541
606
|
const attendeeSearchUrl = meeting?.locusInfo?.links?.resources?.attendeeSearch?.url;
|
|
542
607
|
if (!attendeeSearchUrl) {
|
|
543
|
-
LoggerProxy.logger.error(
|
|
608
|
+
LoggerProxy.logger.error(
|
|
609
|
+
'Meeting:webinar5k#searchLargeScaleWebinarAttendees failed --> attendee search url unavailable'
|
|
610
|
+
);
|
|
544
611
|
throw new Error('Meeting:webinar5k#Attendee search url is not available');
|
|
545
612
|
}
|
|
546
613
|
|
|
@@ -27,6 +27,7 @@ describe('plugin-meetings', () => {
|
|
|
27
27
|
beforeEach(() => {
|
|
28
28
|
request = {
|
|
29
29
|
request: sinon.stub().returns(Promise.resolve()),
|
|
30
|
+
locusDeltaRequest: sinon.stub().returns(Promise.resolve()),
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
manager = new ControlsOptionsManager(request);
|
|
@@ -59,11 +60,11 @@ describe('plugin-meetings', () => {
|
|
|
59
60
|
|
|
60
61
|
const result = manager.setMuteOnEntry(true);
|
|
61
62
|
|
|
62
|
-
assert.calledWith(request.
|
|
63
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
63
64
|
body: { muteOnEntry: { enabled: true } },
|
|
64
65
|
method: HTTP_VERBS.PATCH});
|
|
65
66
|
|
|
66
|
-
assert.deepEqual(result, request.
|
|
67
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
67
68
|
});
|
|
68
69
|
|
|
69
70
|
it('can set mute on entry when the display hint is available enabled=false', () => {
|
|
@@ -71,11 +72,11 @@ describe('plugin-meetings', () => {
|
|
|
71
72
|
|
|
72
73
|
const result = manager.setMuteOnEntry(false);
|
|
73
74
|
|
|
74
|
-
assert.calledWith(request.
|
|
75
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
75
76
|
body: { muteOnEntry: { enabled: false } },
|
|
76
77
|
method: HTTP_VERBS.PATCH});
|
|
77
78
|
|
|
78
|
-
assert.deepEqual(result, request.
|
|
79
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
79
80
|
});
|
|
80
81
|
|
|
81
82
|
it('should send setMuteOnEntry to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
@@ -84,11 +85,11 @@ describe('plugin-meetings', () => {
|
|
|
84
85
|
|
|
85
86
|
const result = manager.setMuteOnEntry(true);
|
|
86
87
|
|
|
87
|
-
assert.calledWith(request.
|
|
88
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
88
89
|
body: { muteOnEntry: { enabled: true } },
|
|
89
90
|
method: HTTP_VERBS.PATCH});
|
|
90
91
|
|
|
91
|
-
assert.deepEqual(result, request.
|
|
92
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
92
93
|
});
|
|
93
94
|
});
|
|
94
95
|
|
|
@@ -114,11 +115,11 @@ describe('plugin-meetings', () => {
|
|
|
114
115
|
|
|
115
116
|
const result = manager.setDisallowUnmute(true);
|
|
116
117
|
|
|
117
|
-
assert.calledWith(request.
|
|
118
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
118
119
|
body: { disallowUnmute: { enabled: true } },
|
|
119
120
|
method: HTTP_VERBS.PATCH});
|
|
120
121
|
|
|
121
|
-
assert.deepEqual(result, request.
|
|
122
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
122
123
|
});
|
|
123
124
|
|
|
124
125
|
it('can set allow unmute when DISABLE_HARD_MUTE display hint is available', () => {
|
|
@@ -126,11 +127,11 @@ describe('plugin-meetings', () => {
|
|
|
126
127
|
|
|
127
128
|
const result = manager.setDisallowUnmute(false);
|
|
128
129
|
|
|
129
|
-
assert.calledWith(request.
|
|
130
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
130
131
|
body: { disallowUnmute: { enabled: false } },
|
|
131
132
|
method: HTTP_VERBS.PATCH});
|
|
132
133
|
|
|
133
|
-
assert.deepEqual(result, request.
|
|
134
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
134
135
|
});
|
|
135
136
|
|
|
136
137
|
it('should send setDisallowUnmute to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
@@ -139,11 +140,11 @@ describe('plugin-meetings', () => {
|
|
|
139
140
|
|
|
140
141
|
const result = manager.setDisallowUnmute(true);
|
|
141
142
|
|
|
142
|
-
assert.calledWith(request.
|
|
143
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
143
144
|
body: { disallowUnmute: { enabled: true } },
|
|
144
145
|
method: HTTP_VERBS.PATCH});
|
|
145
146
|
|
|
146
|
-
assert.deepEqual(result, request.
|
|
147
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
147
148
|
});
|
|
148
149
|
});
|
|
149
150
|
});
|
|
@@ -154,6 +155,7 @@ describe('plugin-meetings', () => {
|
|
|
154
155
|
beforeEach(() => {
|
|
155
156
|
request = {
|
|
156
157
|
request: sinon.stub().resolves(),
|
|
158
|
+
locusDeltaRequest: sinon.stub().resolves(),
|
|
157
159
|
};
|
|
158
160
|
|
|
159
161
|
manager = new ControlsOptionsManager(request);
|
|
@@ -202,7 +204,7 @@ describe('plugin-meetings', () => {
|
|
|
202
204
|
|
|
203
205
|
return manager.update(audio, reactions)
|
|
204
206
|
.then(() => {
|
|
205
|
-
assert.calledWith(request.
|
|
207
|
+
assert.calledWith(request.locusDeltaRequest, {
|
|
206
208
|
uri: 'test/id/controls',
|
|
207
209
|
body: {
|
|
208
210
|
audio: audio.properties,
|
|
@@ -210,7 +212,7 @@ describe('plugin-meetings', () => {
|
|
|
210
212
|
method: HTTP_VERBS.PATCH,
|
|
211
213
|
});
|
|
212
214
|
|
|
213
|
-
assert.calledWith(request.
|
|
215
|
+
assert.calledWith(request.locusDeltaRequest, {
|
|
214
216
|
uri: 'test/id/controls',
|
|
215
217
|
body: {
|
|
216
218
|
reactions: reactions.properties,
|
|
@@ -253,7 +255,7 @@ describe('plugin-meetings', () => {
|
|
|
253
255
|
return manager.update(audio, reactions)
|
|
254
256
|
.then(() => {
|
|
255
257
|
// Audio controls go directly to current locusUrl (no cross-locus authorization)
|
|
256
|
-
assert.calledWith(request.
|
|
258
|
+
assert.calledWith(request.locusDeltaRequest, {
|
|
257
259
|
uri: 'test/id/controls',
|
|
258
260
|
body: {
|
|
259
261
|
audio: audio.properties,
|
|
@@ -284,7 +286,7 @@ describe('plugin-meetings', () => {
|
|
|
284
286
|
|
|
285
287
|
return manager.update(audio)
|
|
286
288
|
.then(() => {
|
|
287
|
-
assert.calledWith(request.
|
|
289
|
+
assert.calledWith(request.locusDeltaRequest, {
|
|
288
290
|
uri: 'test/id/controls',
|
|
289
291
|
body: {
|
|
290
292
|
audio: audio.properties,
|
|
@@ -324,6 +326,7 @@ describe('plugin-meetings', () => {
|
|
|
324
326
|
beforeEach(() => {
|
|
325
327
|
request = {
|
|
326
328
|
request: sinon.stub().returns(Promise.resolve()),
|
|
329
|
+
locusDeltaRequest: sinon.stub().returns(Promise.resolve()),
|
|
327
330
|
};
|
|
328
331
|
|
|
329
332
|
manager = new ControlsOptionsManager(request);
|
|
@@ -368,11 +371,11 @@ describe('plugin-meetings', () => {
|
|
|
368
371
|
|
|
369
372
|
const result = manager.setMuteAll(true, true, true);
|
|
370
373
|
|
|
371
|
-
assert.calledWith(request.
|
|
374
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
372
375
|
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
|
|
373
376
|
method: HTTP_VERBS.PATCH});
|
|
374
377
|
|
|
375
|
-
assert.deepEqual(result, request.
|
|
378
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
376
379
|
});
|
|
377
380
|
|
|
378
381
|
it('can set mute all when the display hint is available mutedEnabled=true', () => {
|
|
@@ -380,11 +383,11 @@ describe('plugin-meetings', () => {
|
|
|
380
383
|
|
|
381
384
|
const result = manager.setMuteAll(true, true, true);
|
|
382
385
|
|
|
383
|
-
assert.calledWith(request.
|
|
386
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
384
387
|
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
|
|
385
388
|
method: HTTP_VERBS.PATCH});
|
|
386
389
|
|
|
387
|
-
assert.deepEqual(result, request.
|
|
390
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
388
391
|
});
|
|
389
392
|
|
|
390
393
|
it('can set mute all when the display hint is available mutedEnabled=true', () => {
|
|
@@ -392,11 +395,11 @@ describe('plugin-meetings', () => {
|
|
|
392
395
|
|
|
393
396
|
const result = manager.setMuteAll(true, true, true);
|
|
394
397
|
|
|
395
|
-
assert.calledWith(request.
|
|
398
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
396
399
|
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true } },
|
|
397
400
|
method: HTTP_VERBS.PATCH});
|
|
398
401
|
|
|
399
|
-
assert.deepEqual(result, request.
|
|
402
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
400
403
|
});
|
|
401
404
|
|
|
402
405
|
it('can set mute all when the display hint is available mutedEnabled=false', () => {
|
|
@@ -404,11 +407,11 @@ describe('plugin-meetings', () => {
|
|
|
404
407
|
|
|
405
408
|
const result = manager.setMuteAll(false, false, false);
|
|
406
409
|
|
|
407
|
-
assert.calledWith(request.
|
|
410
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
408
411
|
body: { audio: { muted: false, disallowUnmute: false, muteOnEntry: false } },
|
|
409
412
|
method: HTTP_VERBS.PATCH});
|
|
410
413
|
|
|
411
|
-
assert.deepEqual(result, request.
|
|
414
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
412
415
|
});
|
|
413
416
|
|
|
414
417
|
it('can set mute all panelists when the display hint is available mutedEnabled=true', () => {
|
|
@@ -416,11 +419,11 @@ describe('plugin-meetings', () => {
|
|
|
416
419
|
|
|
417
420
|
const result = manager.setMuteAll(true, true, true, ['panelist']);
|
|
418
421
|
|
|
419
|
-
assert.calledWith(request.
|
|
422
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
420
423
|
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['panelist'] } },
|
|
421
424
|
method: HTTP_VERBS.PATCH});
|
|
422
425
|
|
|
423
|
-
assert.deepEqual(result, request.
|
|
426
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
424
427
|
});
|
|
425
428
|
|
|
426
429
|
it('can set mute all attendees when the display hint is available mutedEnabled=true', () => {
|
|
@@ -428,11 +431,11 @@ describe('plugin-meetings', () => {
|
|
|
428
431
|
|
|
429
432
|
const result = manager.setMuteAll(true, true, true, ['attendee']);
|
|
430
433
|
|
|
431
|
-
assert.calledWith(request.
|
|
434
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
432
435
|
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] } },
|
|
433
436
|
method: HTTP_VERBS.PATCH});
|
|
434
437
|
|
|
435
|
-
assert.deepEqual(result, request.
|
|
438
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
436
439
|
});
|
|
437
440
|
|
|
438
441
|
it('should send setMuteAll to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
@@ -441,11 +444,11 @@ describe('plugin-meetings', () => {
|
|
|
441
444
|
|
|
442
445
|
const result = manager.setMuteAll(true, true, true, ['attendee']);
|
|
443
446
|
|
|
444
|
-
assert.calledWith(request.
|
|
447
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
445
448
|
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['attendee'] } },
|
|
446
449
|
method: HTTP_VERBS.PATCH});
|
|
447
450
|
|
|
448
|
-
assert.deepEqual(result, request.
|
|
451
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
449
452
|
});
|
|
450
453
|
|
|
451
454
|
it('should send setMuteAll with PANELIST role to locusUrl without authorizingLocusUrl when in breakout', () => {
|
|
@@ -454,11 +457,11 @@ describe('plugin-meetings', () => {
|
|
|
454
457
|
|
|
455
458
|
const result = manager.setMuteAll(true, true, true, ['PANELIST']);
|
|
456
459
|
|
|
457
|
-
assert.calledWith(request.
|
|
460
|
+
assert.calledWith(request.locusDeltaRequest, { uri: 'test/id/controls',
|
|
458
461
|
body: { audio: { muted: true, disallowUnmute: true, muteOnEntry: true, roles: ['PANELIST'] } },
|
|
459
462
|
method: HTTP_VERBS.PATCH});
|
|
460
463
|
|
|
461
|
-
assert.deepEqual(result, request.
|
|
464
|
+
assert.deepEqual(result, request.locusDeltaRequest.firstCall.returnValue);
|
|
462
465
|
});
|
|
463
466
|
});
|
|
464
467
|
});
|