@webex/plugin-meetings 3.12.0-next.6 → 3.12.0-next.7
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/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/meeting/index.js +370 -290
- package/dist/meeting/index.js.map +1 -1
- package/dist/types/meeting/index.d.ts +11 -0
- package/dist/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +1 -1
- package/src/meeting/index.ts +84 -3
- package/src/webinar/index.ts +75 -1
- package/test/unit/spec/meeting/index.js +43 -28
- package/test/unit/spec/webinar/index.ts +60 -0
|
@@ -1982,11 +1982,12 @@ describe('plugin-meetings', () => {
|
|
|
1982
1982
|
describe('#handleLLMOnline', () => {
|
|
1983
1983
|
beforeEach(() => {
|
|
1984
1984
|
webex.internal.llm.off = sinon.stub();
|
|
1985
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
1986
|
+
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
1985
1987
|
});
|
|
1986
1988
|
|
|
1987
|
-
it('
|
|
1989
|
+
it('emits transcription connected events', () => {
|
|
1988
1990
|
meeting.handleLLMOnline();
|
|
1989
|
-
assert.calledOnceWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
1990
1991
|
assert.calledWith(
|
|
1991
1992
|
TriggerProxy.trigger,
|
|
1992
1993
|
sinon.match.instanceOf(Meeting),
|
|
@@ -1997,6 +1998,24 @@ describe('plugin-meetings', () => {
|
|
|
1997
1998
|
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
|
|
1998
1999
|
);
|
|
1999
2000
|
});
|
|
2001
|
+
|
|
2002
|
+
it('restores transcription subscription when caption intent is enabled', () => {
|
|
2003
|
+
webex.internal.voicea.getIsCaptionBoxOn.returns(true);
|
|
2004
|
+
|
|
2005
|
+
meeting.handleLLMOnline();
|
|
2006
|
+
|
|
2007
|
+
assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, {
|
|
2008
|
+
subscribe: ['transcription'],
|
|
2009
|
+
});
|
|
2010
|
+
});
|
|
2011
|
+
|
|
2012
|
+
it('does not restore transcription subscription when caption intent is disabled', () => {
|
|
2013
|
+
webex.internal.voicea.getIsCaptionBoxOn.returns(false);
|
|
2014
|
+
|
|
2015
|
+
meeting.handleLLMOnline();
|
|
2016
|
+
|
|
2017
|
+
assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
|
|
2018
|
+
});
|
|
2000
2019
|
});
|
|
2001
2020
|
|
|
2002
2021
|
describe('#join', () => {
|
|
@@ -2016,6 +2035,7 @@ describe('plugin-meetings', () => {
|
|
|
2016
2035
|
it('should have #join', () => {
|
|
2017
2036
|
assert.exists(meeting.join);
|
|
2018
2037
|
});
|
|
2038
|
+
|
|
2019
2039
|
beforeEach(() => {
|
|
2020
2040
|
setCorrelationIdSpy = sinon.spy(meeting, 'setCorrelationId');
|
|
2021
2041
|
meeting.setLocus = sinon.stub().returns(true);
|
|
@@ -2169,7 +2189,6 @@ describe('plugin-meetings', () => {
|
|
|
2169
2189
|
await meeting.join().catch(() => {
|
|
2170
2190
|
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
2171
2191
|
|
|
2172
|
-
// Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
|
|
2173
2192
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
2174
2193
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2175
2194
|
name: 'client.call.initiated',
|
|
@@ -2201,6 +2220,7 @@ describe('plugin-meetings', () => {
|
|
|
2201
2220
|
});
|
|
2202
2221
|
});
|
|
2203
2222
|
});
|
|
2223
|
+
|
|
2204
2224
|
describe('lmm, transcription & permissionTokenRefresh decoupling', () => {
|
|
2205
2225
|
beforeEach(() => {
|
|
2206
2226
|
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
|
|
@@ -2271,7 +2291,6 @@ describe('plugin-meetings', () => {
|
|
|
2271
2291
|
const locusInfoParseStub = sinon.stub(meeting.locusInfo, 'parse');
|
|
2272
2292
|
sinon.stub(meeting, 'isJoined').returns(true);
|
|
2273
2293
|
|
|
2274
|
-
// Set up llm.on stub to capture the registered listener when updateLLMConnection is called
|
|
2275
2294
|
let locusLLMEventListener;
|
|
2276
2295
|
meeting.webex.internal.llm.on = sinon.stub().callsFake((eventName, callback) => {
|
|
2277
2296
|
if (eventName === 'event:locus.state_message') {
|
|
@@ -2280,16 +2299,12 @@ describe('plugin-meetings', () => {
|
|
|
2280
2299
|
});
|
|
2281
2300
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
2282
2301
|
|
|
2283
|
-
// we need the real meeting.updateLLMConnection not the mock
|
|
2284
2302
|
meeting.updateLLMConnection.restore();
|
|
2285
2303
|
|
|
2286
|
-
// Call updateLLMConnection to register the listener
|
|
2287
2304
|
await meeting.updateLLMConnection();
|
|
2288
2305
|
|
|
2289
|
-
// Verify the listener was registered and we captured it
|
|
2290
2306
|
assert.isDefined(locusLLMEventListener, 'LLM event listener should be registered');
|
|
2291
2307
|
|
|
2292
|
-
// Now trigger the event
|
|
2293
2308
|
const eventData = {
|
|
2294
2309
|
eventType: 'locus.state_message',
|
|
2295
2310
|
stateElementsMessage: {
|
|
@@ -2309,13 +2324,10 @@ describe('plugin-meetings', () => {
|
|
|
2309
2324
|
sinon.stub(meeting.webex.internal.llm, 'hasEverConnected').value(true);
|
|
2310
2325
|
sinon.stub(meeting.webex.internal.llm, 'registerAndConnect').resolves({});
|
|
2311
2326
|
|
|
2312
|
-
// Restore the real updateLLMConnection
|
|
2313
2327
|
meeting.updateLLMConnection.restore();
|
|
2314
2328
|
|
|
2315
|
-
// Call updateLLMConnection to start the timer
|
|
2316
2329
|
await meeting.updateLLMConnection();
|
|
2317
2330
|
|
|
2318
|
-
// Fast forward time by 3 minutes
|
|
2319
2331
|
fakeClock.tick(3 * 60 * 1000);
|
|
2320
2332
|
|
|
2321
2333
|
assert.calledWith(
|
|
@@ -2340,18 +2352,14 @@ describe('plugin-meetings', () => {
|
|
|
2340
2352
|
.stub(meeting.webex.internal.llm, 'getDatachannelUrl')
|
|
2341
2353
|
.returns('https://datachannel1.example.com');
|
|
2342
2354
|
|
|
2343
|
-
// Restore the real updateLLMConnection
|
|
2344
2355
|
meeting.updateLLMConnection.restore();
|
|
2345
2356
|
|
|
2346
|
-
// First, connect LLM and start the timer
|
|
2347
2357
|
isJoinedStub.returns(true);
|
|
2348
2358
|
meeting.webex.internal.llm.isConnected.returns(false);
|
|
2349
2359
|
await meeting.updateLLMConnection();
|
|
2350
2360
|
|
|
2351
|
-
// Verify timer was started
|
|
2352
2361
|
assert.exists(meeting.llmHealthCheckTimer);
|
|
2353
2362
|
|
|
2354
|
-
// Now simulate that we're no longer joined
|
|
2355
2363
|
isJoinedStub.returns(false);
|
|
2356
2364
|
meeting.webex.internal.llm.isConnected.returns(true);
|
|
2357
2365
|
|
|
@@ -2359,10 +2367,8 @@ describe('plugin-meetings', () => {
|
|
|
2359
2367
|
|
|
2360
2368
|
assert.calledOnce(meeting.webex.internal.llm.disconnectLLM);
|
|
2361
2369
|
|
|
2362
|
-
// Verify the timer was cleared (should be undefined)
|
|
2363
2370
|
assert.isUndefined(meeting.llmHealthCheckTimer);
|
|
2364
2371
|
|
|
2365
|
-
// Fast forward time to ensure no metric is sent
|
|
2366
2372
|
Metrics.sendBehavioralMetric.resetHistory();
|
|
2367
2373
|
fakeClock.tick(3 * 60 * 1000);
|
|
2368
2374
|
|
|
@@ -2397,7 +2403,6 @@ describe('plugin-meetings', () => {
|
|
|
2397
2403
|
.stub()
|
|
2398
2404
|
.rejects(new CaptchaError('bad captcha'));
|
|
2399
2405
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2400
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil, 'joinMeetingOptions');
|
|
2401
2406
|
|
|
2402
2407
|
try {
|
|
2403
2408
|
await meeting.join();
|
|
@@ -2411,8 +2416,7 @@ describe('plugin-meetings', () => {
|
|
|
2411
2416
|
);
|
|
2412
2417
|
assert.instanceOf(error, CaptchaError);
|
|
2413
2418
|
assert.equal(error.message, 'bad captcha');
|
|
2414
|
-
|
|
2415
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2419
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2416
2420
|
}
|
|
2417
2421
|
});
|
|
2418
2422
|
|
|
@@ -2421,7 +2425,6 @@ describe('plugin-meetings', () => {
|
|
|
2421
2425
|
.stub()
|
|
2422
2426
|
.rejects(new PasswordError('bad password'));
|
|
2423
2427
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2424
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2425
2428
|
|
|
2426
2429
|
try {
|
|
2427
2430
|
await meeting.join();
|
|
@@ -2435,8 +2438,7 @@ describe('plugin-meetings', () => {
|
|
|
2435
2438
|
);
|
|
2436
2439
|
assert.instanceOf(error, PasswordError);
|
|
2437
2440
|
assert.equal(error.message, 'bad password');
|
|
2438
|
-
|
|
2439
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2441
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2440
2442
|
}
|
|
2441
2443
|
});
|
|
2442
2444
|
|
|
@@ -2445,7 +2447,6 @@ describe('plugin-meetings', () => {
|
|
|
2445
2447
|
.stub()
|
|
2446
2448
|
.rejects(new PermissionError('bad permission'));
|
|
2447
2449
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2448
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2449
2450
|
|
|
2450
2451
|
try {
|
|
2451
2452
|
await meeting.join();
|
|
@@ -2459,14 +2460,14 @@ describe('plugin-meetings', () => {
|
|
|
2459
2460
|
);
|
|
2460
2461
|
assert.instanceOf(error, PermissionError);
|
|
2461
2462
|
assert.equal(error.message, 'bad permission');
|
|
2462
|
-
|
|
2463
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2463
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2464
2464
|
}
|
|
2465
2465
|
});
|
|
2466
2466
|
});
|
|
2467
2467
|
});
|
|
2468
2468
|
});
|
|
2469
2469
|
|
|
2470
|
+
|
|
2470
2471
|
describe('#addMedia', () => {
|
|
2471
2472
|
const muteStateStub = {
|
|
2472
2473
|
handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
|
|
@@ -10416,14 +10417,24 @@ describe('plugin-meetings', () => {
|
|
|
10416
10417
|
);
|
|
10417
10418
|
done();
|
|
10418
10419
|
});
|
|
10419
|
-
it('listens to the self admitted guest event', (
|
|
10420
|
+
it('listens to the self admitted guest event without blocking on token prefetch', async () => {
|
|
10420
10421
|
meeting.stopKeepAlive = sinon.stub();
|
|
10421
10422
|
meeting.updateLLMConnection = sinon.stub();
|
|
10423
|
+
let resolvePrefetch;
|
|
10424
|
+
|
|
10425
|
+
meeting.ensureDefaultDatachannelTokenAfterAdmit = sinon
|
|
10426
|
+
.stub()
|
|
10427
|
+
.returns(new Promise((resolve) => {
|
|
10428
|
+
resolvePrefetch = resolve;
|
|
10429
|
+
}));
|
|
10422
10430
|
meeting.rtcMetrics = {
|
|
10423
10431
|
sendNextMetrics: sinon.stub(),
|
|
10424
10432
|
};
|
|
10433
|
+
|
|
10425
10434
|
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
10435
|
+
|
|
10426
10436
|
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
10437
|
+
assert.calledOnceWithExactly(meeting.ensureDefaultDatachannelTokenAfterAdmit);
|
|
10427
10438
|
assert.calledThrice(TriggerProxy.trigger);
|
|
10428
10439
|
assert.calledWith(
|
|
10429
10440
|
TriggerProxy.trigger,
|
|
@@ -10442,7 +10453,11 @@ describe('plugin-meetings', () => {
|
|
|
10442
10453
|
correlation_id: meeting.correlationId,
|
|
10443
10454
|
}
|
|
10444
10455
|
);
|
|
10445
|
-
|
|
10456
|
+
|
|
10457
|
+
resolvePrefetch(false);
|
|
10458
|
+
await Promise.resolve();
|
|
10459
|
+
|
|
10460
|
+
assert.calledOnce(meeting.updateLLMConnection);
|
|
10446
10461
|
});
|
|
10447
10462
|
|
|
10448
10463
|
it('listens to the breakouts changed event', () => {
|
|
@@ -33,6 +33,7 @@ describe('plugin-meetings', () => {
|
|
|
33
33
|
webex.internal.llm = {
|
|
34
34
|
getDatachannelToken: sinon.stub().returns(undefined),
|
|
35
35
|
setDatachannelToken: sinon.stub(),
|
|
36
|
+
isDataChannelTokenEnabled: sinon.stub().resolves(false),
|
|
36
37
|
isConnected: sinon.stub().returns(false),
|
|
37
38
|
disconnectLLM: sinon.stub().resolves(),
|
|
38
39
|
off: sinon.stub(),
|
|
@@ -267,6 +268,65 @@ describe('plugin-meetings', () => {
|
|
|
267
268
|
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
268
269
|
});
|
|
269
270
|
|
|
271
|
+
it('refreshes practice-session token before register when cached token is missing', async () => {
|
|
272
|
+
webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
|
|
273
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().callsFake((tokenType) => {
|
|
274
|
+
if (tokenType === DataChannelTokenType.PracticeSession) return undefined;
|
|
275
|
+
|
|
276
|
+
return undefined;
|
|
277
|
+
});
|
|
278
|
+
meeting.refreshDataChannelToken = sinon.stub().resolves({
|
|
279
|
+
body: {
|
|
280
|
+
datachannelToken: 'ps-token-from-refresh',
|
|
281
|
+
dataChannelTokenType: DataChannelTokenType.PracticeSession,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
await webinar.updatePSDataChannel();
|
|
286
|
+
|
|
287
|
+
assert.calledOnceWithExactly(meeting.refreshDataChannelToken);
|
|
288
|
+
assert.calledWithExactly(
|
|
289
|
+
webex.internal.llm.setDatachannelToken,
|
|
290
|
+
'ps-token-from-refresh',
|
|
291
|
+
DataChannelTokenType.PracticeSession
|
|
292
|
+
);
|
|
293
|
+
assert.calledWith(
|
|
294
|
+
webex.internal.llm.registerAndConnect,
|
|
295
|
+
'locus-url',
|
|
296
|
+
'dc-url',
|
|
297
|
+
'ps-token-from-refresh',
|
|
298
|
+
LLM_PRACTICE_SESSION
|
|
299
|
+
);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('does not reconnect if practice-session eligibility changes during async token refresh', async () => {
|
|
303
|
+
webex.internal.llm.isDataChannelTokenEnabled.resolves(true);
|
|
304
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
|
|
305
|
+
|
|
306
|
+
let resolveRefresh;
|
|
307
|
+
meeting.refreshDataChannelToken = sinon.stub().returns(
|
|
308
|
+
new Promise((resolve) => {
|
|
309
|
+
resolveRefresh = resolve;
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
const updatePromise = webinar.updatePSDataChannel();
|
|
314
|
+
|
|
315
|
+
webinar.practiceSessionEnabled = false;
|
|
316
|
+
|
|
317
|
+
resolveRefresh({
|
|
318
|
+
body: {
|
|
319
|
+
datachannelToken: 'stale-ps-token',
|
|
320
|
+
dataChannelTokenType: DataChannelTokenType.PracticeSession,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const result = await updatePromise;
|
|
325
|
+
|
|
326
|
+
assert.isUndefined(result);
|
|
327
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
328
|
+
});
|
|
329
|
+
|
|
270
330
|
it('no-ops when practice session join eligibility is false', async () => {
|
|
271
331
|
webinar.practiceSessionEnabled = false;
|
|
272
332
|
const cleanupPSDataChannelStub = sinon.stub(webinar, 'cleanupPSDataChannel').resolves();
|