@webex/plugin-meetings 3.11.0-next.43 → 3.11.0-next.44
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/annotation/index.js +11 -2
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +3 -2
- package/dist/constants.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +926 -843
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/util.js +5 -1
- package/dist/meeting/util.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/meeting/index.d.ts +9 -1
- package/dist/webinar/index.js +209 -103
- package/dist/webinar/index.js.map +1 -1
- package/package.json +2 -2
- package/src/annotation/index.ts +21 -4
- package/src/constants.ts +1 -0
- package/src/meeting/index.ts +60 -36
- package/src/meeting/util.ts +4 -1
- package/src/webinar/index.ts +113 -4
- package/test/unit/spec/annotation/index.ts +69 -7
- package/test/unit/spec/meeting/index.js +160 -54
- package/test/unit/spec/meeting/utils.js +5 -4
- package/test/unit/spec/webinar/index.ts +224 -36
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {assert
|
|
1
|
+
import {assert} from '@webex/test-helper-chai';
|
|
2
2
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
3
3
|
import Webinar from '@webex/plugin-meetings/src/webinar';
|
|
4
4
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
5
5
|
import uuid from 'uuid';
|
|
6
6
|
import sinon from 'sinon';
|
|
7
|
+
import {DataChannelTokenType} from '@webex/internal-plugin-llm';
|
|
8
|
+
import {LLM_PRACTICE_SESSION, SHARE_STATUS} from '@webex/plugin-meetings/src/constants';
|
|
7
9
|
|
|
8
10
|
describe('plugin-meetings', () => {
|
|
9
11
|
describe('Webinar', () => {
|
|
@@ -26,7 +28,19 @@ describe('plugin-meetings', () => {
|
|
|
26
28
|
webex.meetings = {};
|
|
27
29
|
webex.credentials.getUserToken = getUserTokenStub;
|
|
28
30
|
webex.meetings.getMeetingByType = sinon.stub();
|
|
31
|
+
webex.internal.voicea.announce = sinon.stub();
|
|
29
32
|
|
|
33
|
+
webex.internal.llm = {
|
|
34
|
+
getDatachannelToken: sinon.stub().returns(undefined),
|
|
35
|
+
setDatachannelToken: sinon.stub(),
|
|
36
|
+
isConnected: sinon.stub().returns(false),
|
|
37
|
+
disconnectLLM: sinon.stub().resolves(),
|
|
38
|
+
off: sinon.stub(),
|
|
39
|
+
on: sinon.stub(),
|
|
40
|
+
getLocusUrl: sinon.stub().returns('old-locus-url'),
|
|
41
|
+
getDatachannelUrl: sinon.stub().returns('old-dc-url'),
|
|
42
|
+
registerAndConnect: sinon.stub().resolves('REGISTER_AND_CONNECT_RESULT'),
|
|
43
|
+
};
|
|
30
44
|
});
|
|
31
45
|
|
|
32
46
|
afterEach(() => {
|
|
@@ -147,20 +161,198 @@ describe('plugin-meetings', () => {
|
|
|
147
161
|
assert.equal(result.isPromoted, false, 'should not indicate promotion');
|
|
148
162
|
assert.equal(result.isDemoted, false, 'should not indicate demotion');
|
|
149
163
|
});
|
|
164
|
+
|
|
165
|
+
it('handles missing role payload safely', () => {
|
|
166
|
+
const updateStatusByRoleStub = sinon.stub(webinar, 'updateStatusByRole');
|
|
167
|
+
|
|
168
|
+
const result = webinar.updateRoleChanged(undefined);
|
|
169
|
+
|
|
170
|
+
assert.equal(webinar.selfIsPanelist, false);
|
|
171
|
+
assert.equal(webinar.selfIsAttendee, false);
|
|
172
|
+
assert.equal(webinar.canManageWebcast, false);
|
|
173
|
+
assert.deepEqual(result, {isPromoted: false, isDemoted: false});
|
|
174
|
+
assert.calledOnceWithExactly(updateStatusByRoleStub, {isPromoted: false, isDemoted: false});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('#cleanUp', () => {
|
|
179
|
+
it('delegates to cleanupPSDataChannel', () => {
|
|
180
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
181
|
+
|
|
182
|
+
webinar.cleanUp();
|
|
183
|
+
|
|
184
|
+
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('#cleanupPSDataChannel', () => {
|
|
189
|
+
let meeting;
|
|
190
|
+
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
meeting = {
|
|
193
|
+
processRelayEvent: sinon.stub(),
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('disconnects the practice session channel and removes the relay listener', async () => {
|
|
200
|
+
await webinar.cleanupPSDataChannel();
|
|
201
|
+
|
|
202
|
+
assert.calledOnceWithExactly(
|
|
203
|
+
webex.internal.llm.disconnectLLM,
|
|
204
|
+
{code: 3050, reason: 'done (permanent)'},
|
|
205
|
+
LLM_PRACTICE_SESSION
|
|
206
|
+
);
|
|
207
|
+
assert.calledOnceWithExactly(
|
|
208
|
+
webex.internal.llm.off,
|
|
209
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
210
|
+
meeting.processRelayEvent
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('#updatePSDataChannel', () => {
|
|
216
|
+
let meeting;
|
|
217
|
+
let processRelayEvent;
|
|
218
|
+
|
|
219
|
+
beforeEach(() => {
|
|
220
|
+
processRelayEvent = sinon.stub();
|
|
221
|
+
meeting = {
|
|
222
|
+
isJoined: sinon.stub().returns(true),
|
|
223
|
+
processRelayEvent,
|
|
224
|
+
locusInfo: {
|
|
225
|
+
url: 'locus-url',
|
|
226
|
+
info: {practiceSessionDatachannelUrl: 'dc-url'},
|
|
227
|
+
self: {practiceSessionDatachannelToken: 'ps-token'},
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
webex.meetings.getMeetingByType = sinon.stub().returns(meeting);
|
|
232
|
+
|
|
233
|
+
// Ensure connect path is eligible
|
|
234
|
+
webinar.selfIsPanelist = true;
|
|
235
|
+
webinar.practiceSessionEnabled = true;
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('no-ops when practice session join eligibility is false', async () => {
|
|
239
|
+
webinar.practiceSessionEnabled = false;
|
|
240
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
241
|
+
|
|
242
|
+
const result = await webinar.updatePSDataChannel();
|
|
243
|
+
|
|
244
|
+
assert.isUndefined(result);
|
|
245
|
+
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
246
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('no-ops when meeting is not joined', async () => {
|
|
250
|
+
meeting.isJoined.returns(false);
|
|
251
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
252
|
+
|
|
253
|
+
const result = await webinar.updatePSDataChannel();
|
|
254
|
+
|
|
255
|
+
assert.isUndefined(result);
|
|
256
|
+
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
257
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('no-ops when practiceSessionDatachannelUrl is missing', async () => {
|
|
261
|
+
meeting.locusInfo.info.practiceSessionDatachannelUrl = undefined;
|
|
262
|
+
|
|
263
|
+
const result = await webinar.updatePSDataChannel();
|
|
264
|
+
|
|
265
|
+
assert.isUndefined(result);
|
|
266
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('no-ops when already connected to the same endpoints', async () => {
|
|
270
|
+
webex.internal.llm.isConnected.returns(true);
|
|
271
|
+
webex.internal.llm.getLocusUrl.returns('locus-url');
|
|
272
|
+
webex.internal.llm.getDatachannelUrl.returns('dc-url');
|
|
273
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
274
|
+
|
|
275
|
+
const result = await webinar.updatePSDataChannel();
|
|
276
|
+
|
|
277
|
+
assert.isUndefined(result);
|
|
278
|
+
assert.notCalled(cleanupPSDataChannelStub);
|
|
279
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('connects when eligible', async () => {
|
|
283
|
+
const result = await webinar.updatePSDataChannel();
|
|
284
|
+
|
|
285
|
+
assert.calledOnceWithExactly(
|
|
286
|
+
webex.internal.llm.setDatachannelToken,
|
|
287
|
+
'ps-token',
|
|
288
|
+
DataChannelTokenType.PracticeSession
|
|
289
|
+
);
|
|
290
|
+
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
291
|
+
assert.calledWith(
|
|
292
|
+
webex.internal.llm.registerAndConnect,
|
|
293
|
+
'locus-url',
|
|
294
|
+
'dc-url',
|
|
295
|
+
'ps-token',
|
|
296
|
+
LLM_PRACTICE_SESSION
|
|
297
|
+
);
|
|
298
|
+
assert.calledOnceWithExactly(webex.internal.voicea.announce);
|
|
299
|
+
assert.equal(result, 'REGISTER_AND_CONNECT_RESULT');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('uses cached token when available', async () => {
|
|
303
|
+
webex.internal.llm.getDatachannelToken.returns('cached-token');
|
|
304
|
+
|
|
305
|
+
await webinar.updatePSDataChannel();
|
|
306
|
+
|
|
307
|
+
assert.calledWithExactly(
|
|
308
|
+
webex.internal.llm.getDatachannelToken,
|
|
309
|
+
DataChannelTokenType.PracticeSession
|
|
310
|
+
);
|
|
311
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
312
|
+
assert.calledWith(
|
|
313
|
+
webex.internal.llm.registerAndConnect,
|
|
314
|
+
'locus-url',
|
|
315
|
+
'dc-url',
|
|
316
|
+
'cached-token',
|
|
317
|
+
LLM_PRACTICE_SESSION
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('cleans up the existing practice session channel before reconnecting to new endpoints', async () => {
|
|
322
|
+
webex.internal.llm.isConnected.returns(true);
|
|
323
|
+
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|
|
324
|
+
|
|
325
|
+
await webinar.updatePSDataChannel();
|
|
326
|
+
|
|
327
|
+
assert.calledOnceWithExactly(cleanupPSDataChannelStub);
|
|
328
|
+
assert.calledOnce(webex.internal.llm.registerAndConnect);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it('rebinds relay listener after successful connect', async () => {
|
|
332
|
+
await webinar.updatePSDataChannel();
|
|
333
|
+
|
|
334
|
+
assert.calledWith(
|
|
335
|
+
webex.internal.llm.off,
|
|
336
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
337
|
+
processRelayEvent
|
|
338
|
+
);
|
|
339
|
+
assert.calledWith(
|
|
340
|
+
webex.internal.llm.on,
|
|
341
|
+
`event:relay.event:${LLM_PRACTICE_SESSION}`,
|
|
342
|
+
processRelayEvent
|
|
343
|
+
);
|
|
344
|
+
});
|
|
150
345
|
});
|
|
151
346
|
|
|
152
347
|
describe('#updateStatusByRole', () => {
|
|
153
|
-
let updateLLMConnection;
|
|
154
348
|
let updateMediaShares;
|
|
155
349
|
beforeEach(() => {
|
|
156
|
-
// @ts-ignore
|
|
157
|
-
updateLLMConnection = sinon.stub();
|
|
158
350
|
updateMediaShares = sinon.stub()
|
|
159
351
|
webinar.webex.meetings = {
|
|
160
352
|
getMeetingByType: sinon.stub().returns({
|
|
161
353
|
id: 'meeting-id',
|
|
162
|
-
updateLLMConnection:
|
|
163
|
-
shareStatus:
|
|
354
|
+
updateLLMConnection: sinon.stub(),
|
|
355
|
+
shareStatus: SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE,
|
|
164
356
|
locusInfo: {
|
|
165
357
|
mediaShares: 'mediaShares',
|
|
166
358
|
updateMediaShares: updateMediaShares
|
|
@@ -173,40 +365,20 @@ describe('plugin-meetings', () => {
|
|
|
173
365
|
sinon.restore();
|
|
174
366
|
});
|
|
175
367
|
|
|
176
|
-
it('trigger updateLLMConnection if PS started', () => {
|
|
177
|
-
|
|
178
|
-
webinar.practiceSessionEnabled = true;
|
|
179
|
-
const roleChange = {isPromoted: true, isDemoted: false};
|
|
180
|
-
|
|
181
|
-
const result = webinar.updateStatusByRole(roleChange);
|
|
182
|
-
|
|
183
|
-
assert.calledOnce(updateLLMConnection);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
it('Not trigger updateLLMConnection if PS not started', () => {
|
|
187
|
-
|
|
188
|
-
webinar.practiceSessionEnabled = false;
|
|
189
|
-
const roleChange = {isPromoted: true, isDemoted: false};
|
|
190
|
-
|
|
191
|
-
const result = webinar.updateStatusByRole(roleChange);
|
|
192
|
-
|
|
193
|
-
assert.notCalled(updateLLMConnection);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
368
|
it('trigger updateMediaShares if promoted', () => {
|
|
197
369
|
|
|
198
370
|
const roleChange = {isPromoted: true, isDemoted: false};
|
|
199
371
|
|
|
200
|
-
|
|
372
|
+
webinar.updateStatusByRole(roleChange);
|
|
201
373
|
|
|
202
|
-
assert.
|
|
374
|
+
assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
|
|
203
375
|
});
|
|
204
376
|
|
|
205
377
|
it('Not trigger updateMediaShares if no role change', () => {
|
|
206
378
|
|
|
207
379
|
const roleChange = {isPromoted: false, isDemoted: false};
|
|
208
380
|
|
|
209
|
-
|
|
381
|
+
webinar.updateStatusByRole(roleChange);
|
|
210
382
|
|
|
211
383
|
assert.notCalled(updateMediaShares);
|
|
212
384
|
});
|
|
@@ -214,18 +386,18 @@ describe('plugin-meetings', () => {
|
|
|
214
386
|
|
|
215
387
|
const roleChange = {isPromoted: true, isDemoted: false};
|
|
216
388
|
|
|
217
|
-
|
|
389
|
+
webinar.updateStatusByRole(roleChange);
|
|
218
390
|
|
|
219
|
-
assert.
|
|
391
|
+
assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
|
|
220
392
|
});
|
|
221
393
|
|
|
222
394
|
it('trigger updateMediaShares if is attendee with whiteboard share', () => {
|
|
223
395
|
|
|
224
396
|
const roleChange = {isPromoted: false, isDemoted: true};
|
|
225
397
|
|
|
226
|
-
|
|
398
|
+
webinar.updateStatusByRole(roleChange);
|
|
227
399
|
|
|
228
|
-
assert.
|
|
400
|
+
assert.calledOnceWithExactly(updateMediaShares, 'mediaShares', true);
|
|
229
401
|
});
|
|
230
402
|
|
|
231
403
|
it('Not trigger updateMediaShares if is attendee with screen share', () => {
|
|
@@ -233,8 +405,8 @@ describe('plugin-meetings', () => {
|
|
|
233
405
|
webinar.webex.meetings = {
|
|
234
406
|
getMeetingByType: sinon.stub().returns({
|
|
235
407
|
id: 'meeting-id',
|
|
236
|
-
updateLLMConnection:
|
|
237
|
-
shareStatus:
|
|
408
|
+
updateLLMConnection: sinon.stub(),
|
|
409
|
+
shareStatus: SHARE_STATUS.REMOTE_SHARE_ACTIVE,
|
|
238
410
|
locusInfo: {
|
|
239
411
|
mediaShares: 'mediaShares',
|
|
240
412
|
updateMediaShares: updateMediaShares
|
|
@@ -244,10 +416,18 @@ describe('plugin-meetings', () => {
|
|
|
244
416
|
|
|
245
417
|
const roleChange = {isPromoted: false, isDemoted: true};
|
|
246
418
|
|
|
247
|
-
|
|
419
|
+
webinar.updateStatusByRole(roleChange);
|
|
248
420
|
|
|
249
421
|
assert.notCalled(updateMediaShares);
|
|
250
422
|
});
|
|
423
|
+
|
|
424
|
+
it('updates PS data channel based on join eligibility', () => {
|
|
425
|
+
const updatePSDataChannelStub = sinon.stub(webinar, 'updatePSDataChannel').resolves();
|
|
426
|
+
|
|
427
|
+
webinar.updateStatusByRole({isPromoted: false, isDemoted: false});
|
|
428
|
+
|
|
429
|
+
assert.calledOnceWithExactly(updatePSDataChannelStub);
|
|
430
|
+
});
|
|
251
431
|
});
|
|
252
432
|
|
|
253
433
|
describe("#setPracticeSessionState", () => {
|
|
@@ -323,6 +503,14 @@ describe('plugin-meetings', () => {
|
|
|
323
503
|
|
|
324
504
|
assert.equal(webinar.practiceSessionEnabled, false);
|
|
325
505
|
});
|
|
506
|
+
it('triggers PS data channel update using computed eligibility', () => {
|
|
507
|
+
webinar.selfIsPanelist = true;
|
|
508
|
+
const updatePSDataChannelStub = sinon.stub(webinar, 'updatePSDataChannel').resolves();
|
|
509
|
+
|
|
510
|
+
webinar.updatePracticeSessionStatus({enabled: true});
|
|
511
|
+
|
|
512
|
+
assert.calledOnceWithExactly(updatePSDataChannelStub);
|
|
513
|
+
});
|
|
326
514
|
});
|
|
327
515
|
|
|
328
516
|
describe("#startWebcast", () => {
|