@webex/plugin-meetings 3.12.0-next.4 → 3.12.0-next.41
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/AGENTS.md +9 -0
- package/dist/aiEnableRequest/index.js +15 -2
- package/dist/aiEnableRequest/index.js.map +1 -1
- package/dist/breakouts/breakout.js +6 -2
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +1 -1
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +11 -1
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/index.js +23 -21
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +91 -0
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/hashTree/constants.js +10 -1
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +554 -350
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/utils.js +22 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/interceptors/locusRetry.js +23 -8
- package/dist/interceptors/locusRetry.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +274 -85
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/types.js +16 -0
- package/dist/locus-info/types.js.map +1 -1
- package/dist/meeting/index.js +710 -499
- 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 +174 -77
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +49 -5
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +3 -0
- package/dist/member/util.js.map +1 -1
- package/dist/types/controls-options-manager/constants.d.ts +6 -1
- package/dist/types/hashTree/constants.d.ts +1 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +53 -15
- package/dist/types/hashTree/utils.d.ts +11 -0
- package/dist/types/interceptors/locusRetry.d.ts +4 -4
- package/dist/types/locus-info/index.d.ts +46 -6
- package/dist/types/locus-info/types.d.ts +17 -1
- package/dist/types/meeting/index.d.ts +64 -1
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +1 -0
- package/dist/types/member/util.d.ts +1 -0
- package/dist/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +22 -22
- package/src/aiEnableRequest/index.ts +16 -0
- package/src/breakouts/breakout.ts +2 -1
- package/src/constants.ts +1 -1
- package/src/controls-options-manager/constants.ts +14 -1
- package/src/controls-options-manager/index.ts +26 -19
- package/src/controls-options-manager/util.ts +81 -1
- package/src/hashTree/constants.ts +9 -0
- package/src/hashTree/hashTreeParser.ts +278 -160
- package/src/hashTree/utils.ts +17 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/locus-info/index.ts +274 -93
- package/src/locus-info/types.ts +19 -1
- package/src/meeting/index.ts +206 -22
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +77 -43
- package/src/meetings/util.ts +56 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/webinar/index.ts +75 -1
- package/test/unit/spec/aiEnableRequest/index.ts +86 -0
- package/test/unit/spec/breakouts/breakout.ts +7 -3
- package/test/unit/spec/controls-options-manager/index.js +114 -6
- package/test/unit/spec/controls-options-manager/util.js +165 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +996 -51
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/locusRetry.ts +205 -4
- package/test/unit/spec/locus-info/index.js +397 -81
- package/test/unit/spec/meeting/index.js +271 -44
- package/test/unit/spec/meeting/utils.js +4 -0
- package/test/unit/spec/meetings/index.js +195 -13
- package/test/unit/spec/meetings/utils.js +137 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/webinar/index.ts +60 -0
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
ONLINE,
|
|
35
35
|
OFFLINE,
|
|
36
36
|
ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
|
|
37
|
+
LOCUS_LLM_EVENT,
|
|
37
38
|
} from '@webex/plugin-meetings/src/constants';
|
|
38
39
|
import {
|
|
39
40
|
ConnectionState,
|
|
@@ -1982,11 +1983,12 @@ describe('plugin-meetings', () => {
|
|
|
1982
1983
|
describe('#handleLLMOnline', () => {
|
|
1983
1984
|
beforeEach(() => {
|
|
1984
1985
|
webex.internal.llm.off = sinon.stub();
|
|
1986
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
1987
|
+
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
1985
1988
|
});
|
|
1986
1989
|
|
|
1987
|
-
it('
|
|
1990
|
+
it('emits transcription connected events', () => {
|
|
1988
1991
|
meeting.handleLLMOnline();
|
|
1989
|
-
assert.calledOnceWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
1990
1992
|
assert.calledWith(
|
|
1991
1993
|
TriggerProxy.trigger,
|
|
1992
1994
|
sinon.match.instanceOf(Meeting),
|
|
@@ -1997,6 +1999,24 @@ describe('plugin-meetings', () => {
|
|
|
1997
1999
|
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
|
|
1998
2000
|
);
|
|
1999
2001
|
});
|
|
2002
|
+
|
|
2003
|
+
it('restores transcription subscription when caption intent is enabled', () => {
|
|
2004
|
+
webex.internal.voicea.getIsCaptionBoxOn.returns(true);
|
|
2005
|
+
|
|
2006
|
+
meeting.handleLLMOnline();
|
|
2007
|
+
|
|
2008
|
+
assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, {
|
|
2009
|
+
subscribe: ['transcription'],
|
|
2010
|
+
});
|
|
2011
|
+
});
|
|
2012
|
+
|
|
2013
|
+
it('does not restore transcription subscription when caption intent is disabled', () => {
|
|
2014
|
+
webex.internal.voicea.getIsCaptionBoxOn.returns(false);
|
|
2015
|
+
|
|
2016
|
+
meeting.handleLLMOnline();
|
|
2017
|
+
|
|
2018
|
+
assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
|
|
2019
|
+
});
|
|
2000
2020
|
});
|
|
2001
2021
|
|
|
2002
2022
|
describe('#join', () => {
|
|
@@ -2016,6 +2036,7 @@ describe('plugin-meetings', () => {
|
|
|
2016
2036
|
it('should have #join', () => {
|
|
2017
2037
|
assert.exists(meeting.join);
|
|
2018
2038
|
});
|
|
2039
|
+
|
|
2019
2040
|
beforeEach(() => {
|
|
2020
2041
|
setCorrelationIdSpy = sinon.spy(meeting, 'setCorrelationId');
|
|
2021
2042
|
meeting.setLocus = sinon.stub().returns(true);
|
|
@@ -2169,7 +2190,6 @@ describe('plugin-meetings', () => {
|
|
|
2169
2190
|
await meeting.join().catch(() => {
|
|
2170
2191
|
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
2171
2192
|
|
|
2172
|
-
// Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
|
|
2173
2193
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
2174
2194
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2175
2195
|
name: 'client.call.initiated',
|
|
@@ -2201,6 +2221,7 @@ describe('plugin-meetings', () => {
|
|
|
2201
2221
|
});
|
|
2202
2222
|
});
|
|
2203
2223
|
});
|
|
2224
|
+
|
|
2204
2225
|
describe('lmm, transcription & permissionTokenRefresh decoupling', () => {
|
|
2205
2226
|
beforeEach(() => {
|
|
2206
2227
|
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
|
|
@@ -2271,7 +2292,6 @@ describe('plugin-meetings', () => {
|
|
|
2271
2292
|
const locusInfoParseStub = sinon.stub(meeting.locusInfo, 'parse');
|
|
2272
2293
|
sinon.stub(meeting, 'isJoined').returns(true);
|
|
2273
2294
|
|
|
2274
|
-
// Set up llm.on stub to capture the registered listener when updateLLMConnection is called
|
|
2275
2295
|
let locusLLMEventListener;
|
|
2276
2296
|
meeting.webex.internal.llm.on = sinon.stub().callsFake((eventName, callback) => {
|
|
2277
2297
|
if (eventName === 'event:locus.state_message') {
|
|
@@ -2280,16 +2300,12 @@ describe('plugin-meetings', () => {
|
|
|
2280
2300
|
});
|
|
2281
2301
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
2282
2302
|
|
|
2283
|
-
// we need the real meeting.updateLLMConnection not the mock
|
|
2284
2303
|
meeting.updateLLMConnection.restore();
|
|
2285
2304
|
|
|
2286
|
-
// Call updateLLMConnection to register the listener
|
|
2287
2305
|
await meeting.updateLLMConnection();
|
|
2288
2306
|
|
|
2289
|
-
// Verify the listener was registered and we captured it
|
|
2290
2307
|
assert.isDefined(locusLLMEventListener, 'LLM event listener should be registered');
|
|
2291
2308
|
|
|
2292
|
-
// Now trigger the event
|
|
2293
2309
|
const eventData = {
|
|
2294
2310
|
eventType: 'locus.state_message',
|
|
2295
2311
|
stateElementsMessage: {
|
|
@@ -2309,13 +2325,10 @@ describe('plugin-meetings', () => {
|
|
|
2309
2325
|
sinon.stub(meeting.webex.internal.llm, 'hasEverConnected').value(true);
|
|
2310
2326
|
sinon.stub(meeting.webex.internal.llm, 'registerAndConnect').resolves({});
|
|
2311
2327
|
|
|
2312
|
-
// Restore the real updateLLMConnection
|
|
2313
2328
|
meeting.updateLLMConnection.restore();
|
|
2314
2329
|
|
|
2315
|
-
// Call updateLLMConnection to start the timer
|
|
2316
2330
|
await meeting.updateLLMConnection();
|
|
2317
2331
|
|
|
2318
|
-
// Fast forward time by 3 minutes
|
|
2319
2332
|
fakeClock.tick(3 * 60 * 1000);
|
|
2320
2333
|
|
|
2321
2334
|
assert.calledWith(
|
|
@@ -2340,18 +2353,14 @@ describe('plugin-meetings', () => {
|
|
|
2340
2353
|
.stub(meeting.webex.internal.llm, 'getDatachannelUrl')
|
|
2341
2354
|
.returns('https://datachannel1.example.com');
|
|
2342
2355
|
|
|
2343
|
-
// Restore the real updateLLMConnection
|
|
2344
2356
|
meeting.updateLLMConnection.restore();
|
|
2345
2357
|
|
|
2346
|
-
// First, connect LLM and start the timer
|
|
2347
2358
|
isJoinedStub.returns(true);
|
|
2348
2359
|
meeting.webex.internal.llm.isConnected.returns(false);
|
|
2349
2360
|
await meeting.updateLLMConnection();
|
|
2350
2361
|
|
|
2351
|
-
// Verify timer was started
|
|
2352
2362
|
assert.exists(meeting.llmHealthCheckTimer);
|
|
2353
2363
|
|
|
2354
|
-
// Now simulate that we're no longer joined
|
|
2355
2364
|
isJoinedStub.returns(false);
|
|
2356
2365
|
meeting.webex.internal.llm.isConnected.returns(true);
|
|
2357
2366
|
|
|
@@ -2359,10 +2368,8 @@ describe('plugin-meetings', () => {
|
|
|
2359
2368
|
|
|
2360
2369
|
assert.calledOnce(meeting.webex.internal.llm.disconnectLLM);
|
|
2361
2370
|
|
|
2362
|
-
// Verify the timer was cleared (should be undefined)
|
|
2363
2371
|
assert.isUndefined(meeting.llmHealthCheckTimer);
|
|
2364
2372
|
|
|
2365
|
-
// Fast forward time to ensure no metric is sent
|
|
2366
2373
|
Metrics.sendBehavioralMetric.resetHistory();
|
|
2367
2374
|
fakeClock.tick(3 * 60 * 1000);
|
|
2368
2375
|
|
|
@@ -2397,7 +2404,6 @@ describe('plugin-meetings', () => {
|
|
|
2397
2404
|
.stub()
|
|
2398
2405
|
.rejects(new CaptchaError('bad captcha'));
|
|
2399
2406
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2400
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil, 'joinMeetingOptions');
|
|
2401
2407
|
|
|
2402
2408
|
try {
|
|
2403
2409
|
await meeting.join();
|
|
@@ -2411,8 +2417,7 @@ describe('plugin-meetings', () => {
|
|
|
2411
2417
|
);
|
|
2412
2418
|
assert.instanceOf(error, CaptchaError);
|
|
2413
2419
|
assert.equal(error.message, 'bad captcha');
|
|
2414
|
-
|
|
2415
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2420
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2416
2421
|
}
|
|
2417
2422
|
});
|
|
2418
2423
|
|
|
@@ -2421,7 +2426,6 @@ describe('plugin-meetings', () => {
|
|
|
2421
2426
|
.stub()
|
|
2422
2427
|
.rejects(new PasswordError('bad password'));
|
|
2423
2428
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2424
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2425
2429
|
|
|
2426
2430
|
try {
|
|
2427
2431
|
await meeting.join();
|
|
@@ -2435,8 +2439,7 @@ describe('plugin-meetings', () => {
|
|
|
2435
2439
|
);
|
|
2436
2440
|
assert.instanceOf(error, PasswordError);
|
|
2437
2441
|
assert.equal(error.message, 'bad password');
|
|
2438
|
-
|
|
2439
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2442
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2440
2443
|
}
|
|
2441
2444
|
});
|
|
2442
2445
|
|
|
@@ -2445,7 +2448,6 @@ describe('plugin-meetings', () => {
|
|
|
2445
2448
|
.stub()
|
|
2446
2449
|
.rejects(new PermissionError('bad permission'));
|
|
2447
2450
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2448
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2449
2451
|
|
|
2450
2452
|
try {
|
|
2451
2453
|
await meeting.join();
|
|
@@ -2459,14 +2461,14 @@ describe('plugin-meetings', () => {
|
|
|
2459
2461
|
);
|
|
2460
2462
|
assert.instanceOf(error, PermissionError);
|
|
2461
2463
|
assert.equal(error.message, 'bad permission');
|
|
2462
|
-
|
|
2463
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2464
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2464
2465
|
}
|
|
2465
2466
|
});
|
|
2466
2467
|
});
|
|
2467
2468
|
});
|
|
2468
2469
|
});
|
|
2469
2470
|
|
|
2471
|
+
|
|
2470
2472
|
describe('#addMedia', () => {
|
|
2471
2473
|
const muteStateStub = {
|
|
2472
2474
|
handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
|
|
@@ -6428,6 +6430,9 @@ describe('plugin-meetings', () => {
|
|
|
6428
6430
|
|
|
6429
6431
|
meeting.annotation.deregisterEvents = sinon.stub();
|
|
6430
6432
|
webex.internal.llm.off = sinon.stub();
|
|
6433
|
+
webex.internal.mercury.off = sinon.stub();
|
|
6434
|
+
meeting.mercuryOnlineHandler = sinon.stub();
|
|
6435
|
+
meeting.mercuryOfflineHandler = sinon.stub();
|
|
6431
6436
|
|
|
6432
6437
|
// A meeting needs to be joined to leave
|
|
6433
6438
|
meeting.meetingState = 'ACTIVE';
|
|
@@ -6451,6 +6456,67 @@ describe('plugin-meetings', () => {
|
|
|
6451
6456
|
assert.calledOnce(meeting.clearMeetingData);
|
|
6452
6457
|
});
|
|
6453
6458
|
|
|
6459
|
+
it('stops listening for LLM/Mercury and tears down transcription and annotation before calling Locus /leave', async () => {
|
|
6460
|
+
const onlineHandler = meeting.mercuryOnlineHandler;
|
|
6461
|
+
const offlineHandler = meeting.mercuryOfflineHandler;
|
|
6462
|
+
|
|
6463
|
+
await meeting.leave();
|
|
6464
|
+
|
|
6465
|
+
// All llm/mercury consumers (direct listeners, voicea transcription,
|
|
6466
|
+
// annotation) must be detached before the /leave request so that
|
|
6467
|
+
// in-flight events do not trigger unnecessary Locus syncs
|
|
6468
|
+
// (per Locus team recommendation).
|
|
6469
|
+
assert.callOrder(
|
|
6470
|
+
webex.internal.llm.off,
|
|
6471
|
+
webex.internal.mercury.off,
|
|
6472
|
+
meeting.stopTranscription,
|
|
6473
|
+
meeting.annotation.deregisterEvents,
|
|
6474
|
+
meeting.meetingRequest.leaveMeeting
|
|
6475
|
+
);
|
|
6476
|
+
assert.calledWithExactly(
|
|
6477
|
+
webex.internal.llm.off,
|
|
6478
|
+
'event:relay.event',
|
|
6479
|
+
meeting.processRelayEvent
|
|
6480
|
+
);
|
|
6481
|
+
assert.calledWithExactly(
|
|
6482
|
+
webex.internal.llm.off,
|
|
6483
|
+
LOCUS_LLM_EVENT,
|
|
6484
|
+
meeting.processLocusLLMEvent
|
|
6485
|
+
);
|
|
6486
|
+
assert.calledWithExactly(webex.internal.mercury.off, ONLINE, onlineHandler);
|
|
6487
|
+
assert.calledWithExactly(webex.internal.mercury.off, OFFLINE, offlineHandler);
|
|
6488
|
+
assert.isUndefined(meeting.mercuryOnlineHandler);
|
|
6489
|
+
assert.isUndefined(meeting.mercuryOfflineHandler);
|
|
6490
|
+
assert.calledOnceWithExactly(meeting.stopTranscription);
|
|
6491
|
+
assert.calledOnceWithExactly(meeting.annotation.deregisterEvents);
|
|
6492
|
+
assert.isUndefined(meeting.transcription);
|
|
6493
|
+
});
|
|
6494
|
+
|
|
6495
|
+
it('tears down llm/mercury/transcription/annotation even when /leave rejects', async () => {
|
|
6496
|
+
const onlineHandler = meeting.mercuryOnlineHandler;
|
|
6497
|
+
const offlineHandler = meeting.mercuryOfflineHandler;
|
|
6498
|
+
meeting.meetingRequest.leaveMeeting = sinon
|
|
6499
|
+
.stub()
|
|
6500
|
+
.returns(Promise.reject(new Error('leave failed')));
|
|
6501
|
+
|
|
6502
|
+
await meeting.leave().catch(() => {});
|
|
6503
|
+
|
|
6504
|
+
assert.calledWithExactly(
|
|
6505
|
+
webex.internal.llm.off,
|
|
6506
|
+
'event:relay.event',
|
|
6507
|
+
meeting.processRelayEvent
|
|
6508
|
+
);
|
|
6509
|
+
assert.calledWithExactly(
|
|
6510
|
+
webex.internal.llm.off,
|
|
6511
|
+
LOCUS_LLM_EVENT,
|
|
6512
|
+
meeting.processLocusLLMEvent
|
|
6513
|
+
);
|
|
6514
|
+
assert.calledWithExactly(webex.internal.mercury.off, ONLINE, onlineHandler);
|
|
6515
|
+
assert.calledWithExactly(webex.internal.mercury.off, OFFLINE, offlineHandler);
|
|
6516
|
+
assert.calledOnceWithExactly(meeting.stopTranscription);
|
|
6517
|
+
assert.calledOnceWithExactly(meeting.annotation.deregisterEvents);
|
|
6518
|
+
});
|
|
6519
|
+
|
|
6454
6520
|
it('should reset call diagnostic latencies correctly', async () => {
|
|
6455
6521
|
const leave = meeting.leave();
|
|
6456
6522
|
|
|
@@ -8458,6 +8524,9 @@ describe('plugin-meetings', () => {
|
|
|
8458
8524
|
|
|
8459
8525
|
meeting.annotation.deregisterEvents = sinon.stub();
|
|
8460
8526
|
webex.internal.llm.off = sinon.stub();
|
|
8527
|
+
webex.internal.mercury.off = sinon.stub();
|
|
8528
|
+
meeting.mercuryOnlineHandler = sinon.stub();
|
|
8529
|
+
meeting.mercuryOfflineHandler = sinon.stub();
|
|
8461
8530
|
|
|
8462
8531
|
// A meeting needs to be joined to end
|
|
8463
8532
|
meeting.meetingState = 'ACTIVE';
|
|
@@ -8480,6 +8549,66 @@ describe('plugin-meetings', () => {
|
|
|
8480
8549
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
8481
8550
|
assert.calledOnce(meeting?.clearMeetingData);
|
|
8482
8551
|
});
|
|
8552
|
+
|
|
8553
|
+
it('stops listening for LLM/Mercury and tears down transcription and annotation before calling Locus /end', async () => {
|
|
8554
|
+
const onlineHandler = meeting.mercuryOnlineHandler;
|
|
8555
|
+
const offlineHandler = meeting.mercuryOfflineHandler;
|
|
8556
|
+
|
|
8557
|
+
await meeting.endMeetingForAll();
|
|
8558
|
+
|
|
8559
|
+
// All llm/mercury consumers (direct listeners, voicea transcription,
|
|
8560
|
+
// annotation) must be detached before the /end request so that
|
|
8561
|
+
// in-flight events do not trigger unnecessary Locus syncs
|
|
8562
|
+
// (per Locus team recommendation).
|
|
8563
|
+
assert.callOrder(
|
|
8564
|
+
webex.internal.llm.off,
|
|
8565
|
+
webex.internal.mercury.off,
|
|
8566
|
+
meeting.stopTranscription,
|
|
8567
|
+
meeting.annotation.deregisterEvents,
|
|
8568
|
+
meeting.meetingRequest.endMeetingForAll
|
|
8569
|
+
);
|
|
8570
|
+
assert.calledWithExactly(
|
|
8571
|
+
webex.internal.llm.off,
|
|
8572
|
+
'event:relay.event',
|
|
8573
|
+
meeting.processRelayEvent
|
|
8574
|
+
);
|
|
8575
|
+
assert.calledWithExactly(
|
|
8576
|
+
webex.internal.llm.off,
|
|
8577
|
+
LOCUS_LLM_EVENT,
|
|
8578
|
+
meeting.processLocusLLMEvent
|
|
8579
|
+
);
|
|
8580
|
+
assert.calledWithExactly(webex.internal.mercury.off, ONLINE, onlineHandler);
|
|
8581
|
+
assert.calledWithExactly(webex.internal.mercury.off, OFFLINE, offlineHandler);
|
|
8582
|
+
assert.isUndefined(meeting.mercuryOnlineHandler);
|
|
8583
|
+
assert.isUndefined(meeting.mercuryOfflineHandler);
|
|
8584
|
+
assert.calledOnceWithExactly(meeting.stopTranscription);
|
|
8585
|
+
assert.calledOnceWithExactly(meeting.annotation.deregisterEvents);
|
|
8586
|
+
});
|
|
8587
|
+
|
|
8588
|
+
it('tears down llm/mercury/transcription/annotation even when /end rejects', async () => {
|
|
8589
|
+
const onlineHandler = meeting.mercuryOnlineHandler;
|
|
8590
|
+
const offlineHandler = meeting.mercuryOfflineHandler;
|
|
8591
|
+
meeting.meetingRequest.endMeetingForAll = sinon
|
|
8592
|
+
.stub()
|
|
8593
|
+
.returns(Promise.reject(new Error('end failed')));
|
|
8594
|
+
|
|
8595
|
+
await meeting.endMeetingForAll().catch(() => {});
|
|
8596
|
+
|
|
8597
|
+
assert.calledWithExactly(
|
|
8598
|
+
webex.internal.llm.off,
|
|
8599
|
+
'event:relay.event',
|
|
8600
|
+
meeting.processRelayEvent
|
|
8601
|
+
);
|
|
8602
|
+
assert.calledWithExactly(
|
|
8603
|
+
webex.internal.llm.off,
|
|
8604
|
+
LOCUS_LLM_EVENT,
|
|
8605
|
+
meeting.processLocusLLMEvent
|
|
8606
|
+
);
|
|
8607
|
+
assert.calledWithExactly(webex.internal.mercury.off, ONLINE, onlineHandler);
|
|
8608
|
+
assert.calledWithExactly(webex.internal.mercury.off, OFFLINE, offlineHandler);
|
|
8609
|
+
assert.calledOnceWithExactly(meeting.stopTranscription);
|
|
8610
|
+
assert.calledOnceWithExactly(meeting.annotation.deregisterEvents);
|
|
8611
|
+
});
|
|
8483
8612
|
});
|
|
8484
8613
|
|
|
8485
8614
|
describe('#moveTo', () => {
|
|
@@ -10416,14 +10545,24 @@ describe('plugin-meetings', () => {
|
|
|
10416
10545
|
);
|
|
10417
10546
|
done();
|
|
10418
10547
|
});
|
|
10419
|
-
it('listens to the self admitted guest event', (
|
|
10548
|
+
it('listens to the self admitted guest event without blocking on token prefetch', async () => {
|
|
10420
10549
|
meeting.stopKeepAlive = sinon.stub();
|
|
10421
10550
|
meeting.updateLLMConnection = sinon.stub();
|
|
10551
|
+
let resolvePrefetch;
|
|
10552
|
+
|
|
10553
|
+
meeting.ensureDefaultDatachannelTokenAfterAdmit = sinon
|
|
10554
|
+
.stub()
|
|
10555
|
+
.returns(new Promise((resolve) => {
|
|
10556
|
+
resolvePrefetch = resolve;
|
|
10557
|
+
}));
|
|
10422
10558
|
meeting.rtcMetrics = {
|
|
10423
10559
|
sendNextMetrics: sinon.stub(),
|
|
10424
10560
|
};
|
|
10561
|
+
|
|
10425
10562
|
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
10563
|
+
|
|
10426
10564
|
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
10565
|
+
assert.calledOnceWithExactly(meeting.ensureDefaultDatachannelTokenAfterAdmit);
|
|
10427
10566
|
assert.calledThrice(TriggerProxy.trigger);
|
|
10428
10567
|
assert.calledWith(
|
|
10429
10568
|
TriggerProxy.trigger,
|
|
@@ -10442,7 +10581,11 @@ describe('plugin-meetings', () => {
|
|
|
10442
10581
|
correlation_id: meeting.correlationId,
|
|
10443
10582
|
}
|
|
10444
10583
|
);
|
|
10445
|
-
|
|
10584
|
+
|
|
10585
|
+
resolvePrefetch(false);
|
|
10586
|
+
await Promise.resolve();
|
|
10587
|
+
|
|
10588
|
+
assert.calledOnce(meeting.updateLLMConnection);
|
|
10446
10589
|
});
|
|
10447
10590
|
|
|
10448
10591
|
it('listens to the breakouts changed event', () => {
|
|
@@ -11009,6 +11152,7 @@ describe('plugin-meetings', () => {
|
|
|
11009
11152
|
meeting.annotation.locusUrlUpdate = sinon.stub();
|
|
11010
11153
|
meeting.simultaneousInterpretation.locusUrlUpdate = sinon.stub();
|
|
11011
11154
|
meeting.webinar.locusUrlUpdate = sinon.stub();
|
|
11155
|
+
meeting.aiEnableRequest.locusUrlUpdate = sinon.stub();
|
|
11012
11156
|
|
|
11013
11157
|
meeting.locusInfo.emit(
|
|
11014
11158
|
{function: 'test', file: 'test'},
|
|
@@ -11023,6 +11167,7 @@ describe('plugin-meetings', () => {
|
|
|
11023
11167
|
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl, false);
|
|
11024
11168
|
assert.calledWith(meeting.simultaneousInterpretation.locusUrlUpdate, newLocusUrl);
|
|
11025
11169
|
assert.calledWith(meeting.webinar.locusUrlUpdate, newLocusUrl);
|
|
11170
|
+
assert.calledWith(meeting.aiEnableRequest.locusUrlUpdate, newLocusUrl);
|
|
11026
11171
|
assert.equal(meeting.locusUrl, newLocusUrl);
|
|
11027
11172
|
assert(meeting.locusId, '12345');
|
|
11028
11173
|
|
|
@@ -11338,6 +11483,93 @@ describe('plugin-meetings', () => {
|
|
|
11338
11483
|
});
|
|
11339
11484
|
});
|
|
11340
11485
|
|
|
11486
|
+
describe('#finalizeMeetingAfterInitialLocusSetup', () => {
|
|
11487
|
+
it('refreshes destination from synced locus when destination type is LOCUS_ID', () => {
|
|
11488
|
+
const syncedLocus = {url: 'https://locus.example.com/locus/123', info: {topic: 'x'}};
|
|
11489
|
+
|
|
11490
|
+
meeting.destinationType = DESTINATION_TYPE.LOCUS_ID;
|
|
11491
|
+
meeting.destination = {info: {topic: 'old'}};
|
|
11492
|
+
|
|
11493
|
+
meeting.finalizeMeetingAfterInitialLocusSetup(syncedLocus);
|
|
11494
|
+
|
|
11495
|
+
assert.equal(meeting.destination, syncedLocus);
|
|
11496
|
+
});
|
|
11497
|
+
|
|
11498
|
+
it('does not refresh destination when destination type is not LOCUS_ID', () => {
|
|
11499
|
+
const syncedLocus = {url: 'https://locus.example.com/locus/123', info: {topic: 'x'}};
|
|
11500
|
+
const originalDestination = {destination: 'original-destination'};
|
|
11501
|
+
|
|
11502
|
+
meeting.destinationType = DESTINATION_TYPE.CONVERSATION_URL;
|
|
11503
|
+
meeting.destination = originalDestination;
|
|
11504
|
+
|
|
11505
|
+
meeting.finalizeMeetingAfterInitialLocusSetup(syncedLocus);
|
|
11506
|
+
|
|
11507
|
+
assert.equal(meeting.destination, originalDestination);
|
|
11508
|
+
});
|
|
11509
|
+
|
|
11510
|
+
it('fetches meeting info when meetingInfo is empty and destination has info', () => {
|
|
11511
|
+
const fetchMeetingInfoStub = sinon.stub(meeting, 'fetchMeetingInfo').resolves();
|
|
11512
|
+
|
|
11513
|
+
meeting.meetingInfo = {};
|
|
11514
|
+
meeting.destination = {url: 'https://locus.example.com/locus/123', info: {topic: 'x'}};
|
|
11515
|
+
|
|
11516
|
+
meeting.finalizeMeetingAfterInitialLocusSetup({});
|
|
11517
|
+
|
|
11518
|
+
assert.calledOnceWithExactly(fetchMeetingInfoStub, {});
|
|
11519
|
+
});
|
|
11520
|
+
|
|
11521
|
+
it('does not fetch meeting info when destination has no info', () => {
|
|
11522
|
+
const fetchMeetingInfoStub = sinon.stub(meeting, 'fetchMeetingInfo').resolves();
|
|
11523
|
+
|
|
11524
|
+
meeting.meetingInfo = {};
|
|
11525
|
+
meeting.destination = {url: 'https://locus.example.com/locus/123'};
|
|
11526
|
+
|
|
11527
|
+
meeting.finalizeMeetingAfterInitialLocusSetup({});
|
|
11528
|
+
|
|
11529
|
+
assert.notCalled(fetchMeetingInfoStub);
|
|
11530
|
+
});
|
|
11531
|
+
|
|
11532
|
+
it('does not fetch meeting info when meetingInfo is already populated', () => {
|
|
11533
|
+
const fetchMeetingInfoStub = sinon.stub(meeting, 'fetchMeetingInfo').resolves();
|
|
11534
|
+
|
|
11535
|
+
meeting.meetingInfo = {meetingJoinUrl: 'https://example.com/join/abc'};
|
|
11536
|
+
meeting.destination = {url: 'https://locus.example.com/locus/123', info: {topic: 'x'}};
|
|
11537
|
+
|
|
11538
|
+
meeting.finalizeMeetingAfterInitialLocusSetup({});
|
|
11539
|
+
|
|
11540
|
+
assert.notCalled(fetchMeetingInfoStub);
|
|
11541
|
+
});
|
|
11542
|
+
|
|
11543
|
+
it('does not fetch meeting info when delayed fetch timer is already scheduled', () => {
|
|
11544
|
+
const fetchMeetingInfoStub = sinon.stub(meeting, 'fetchMeetingInfo').resolves();
|
|
11545
|
+
|
|
11546
|
+
meeting.meetingInfo = {};
|
|
11547
|
+
meeting.destination = {url: 'https://locus.example.com/locus/123', info: {topic: 'x'}};
|
|
11548
|
+
meeting.fetchMeetingInfoTimeoutId = 42;
|
|
11549
|
+
|
|
11550
|
+
meeting.finalizeMeetingAfterInitialLocusSetup({});
|
|
11551
|
+
|
|
11552
|
+
assert.notCalled(fetchMeetingInfoStub);
|
|
11553
|
+
});
|
|
11554
|
+
|
|
11555
|
+
it('swallows async fetchMeetingInfo errors and logs info', async () => {
|
|
11556
|
+
const error = new Error('fetch failed');
|
|
11557
|
+
|
|
11558
|
+
meeting.meetingInfo = {};
|
|
11559
|
+
meeting.destination = {url: 'https://locus.example.com/locus/123', info: {topic: 'x'}};
|
|
11560
|
+
sinon.stub(meeting, 'fetchMeetingInfo').returns(Promise.reject(error));
|
|
11561
|
+
const loggerInfoStub = sinon.stub(LoggerProxy.logger, 'info');
|
|
11562
|
+
|
|
11563
|
+
await meeting.finalizeMeetingAfterInitialLocusSetup({});
|
|
11564
|
+
|
|
11565
|
+
assert.calledOnce(loggerInfoStub);
|
|
11566
|
+
assert.match(
|
|
11567
|
+
loggerInfoStub.firstCall.args[0],
|
|
11568
|
+
/Meeting:index#finalizeMeetingAfterInitialLocusSetup --> deferred fetchMeetingInfo failed: fetch failed/
|
|
11569
|
+
);
|
|
11570
|
+
});
|
|
11571
|
+
});
|
|
11572
|
+
|
|
11341
11573
|
describe('#emailInput', () => {
|
|
11342
11574
|
it('should set the email input', () => {
|
|
11343
11575
|
assert.notOk(meeting.emailInput);
|
|
@@ -13124,7 +13356,9 @@ describe('plugin-meetings', () => {
|
|
|
13124
13356
|
info: {datachannelUrl: 'a datachannel url'},
|
|
13125
13357
|
};
|
|
13126
13358
|
|
|
13127
|
-
webex.internal.llm.getDatachannelToken
|
|
13359
|
+
webex.internal.llm.getDatachannelToken
|
|
13360
|
+
.withArgs('llm-default-session')
|
|
13361
|
+
.returns('token-123');
|
|
13128
13362
|
|
|
13129
13363
|
await meeting.updateLLMConnection();
|
|
13130
13364
|
|
|
@@ -13209,10 +13443,13 @@ describe('plugin-meetings', () => {
|
|
|
13209
13443
|
meeting.processLocusLLMEvent
|
|
13210
13444
|
);
|
|
13211
13445
|
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13212
|
-
assert.calledOnce(meeting.stopTranscription);
|
|
13213
|
-
assert.isUndefined(meeting.transcription);
|
|
13214
13446
|
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13215
|
-
|
|
13447
|
+
// stopTranscription and annotation.deregisterEvents are not
|
|
13448
|
+
// called here: they run in stopListeningForMeetingEvents()
|
|
13449
|
+
// before /leave to avoid double-emitting
|
|
13450
|
+
// MEETING_STOPPED_RECEIVING_TRANSCRIPTION.
|
|
13451
|
+
assert.notCalled(meeting.stopTranscription);
|
|
13452
|
+
assert.notCalled(meeting.annotation.deregisterEvents);
|
|
13216
13453
|
});
|
|
13217
13454
|
it('continues cleanup when disconnectLLM fails during meeting data cleanup', async () => {
|
|
13218
13455
|
webex.internal.llm.disconnectLLM.rejects(new Error('disconnect failed'));
|
|
@@ -13231,19 +13468,9 @@ describe('plugin-meetings', () => {
|
|
|
13231
13468
|
meeting.processLocusLLMEvent
|
|
13232
13469
|
);
|
|
13233
13470
|
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13234
|
-
assert.calledOnce(meeting.stopTranscription);
|
|
13235
|
-
assert.isUndefined(meeting.transcription);
|
|
13236
|
-
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13237
|
-
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13238
|
-
});
|
|
13239
|
-
it('always calls stopTranscription even when transcription is undefined', async () => {
|
|
13240
|
-
meeting.transcription = undefined;
|
|
13241
|
-
|
|
13242
|
-
await meeting.clearMeetingData();
|
|
13243
|
-
|
|
13244
|
-
assert.calledOnce(meeting.stopTranscription);
|
|
13245
|
-
assert.isUndefined(meeting.transcription);
|
|
13246
13471
|
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13472
|
+
assert.notCalled(meeting.stopTranscription);
|
|
13473
|
+
assert.notCalled(meeting.annotation.deregisterEvents);
|
|
13247
13474
|
});
|
|
13248
13475
|
});
|
|
13249
13476
|
});
|
|
@@ -60,6 +60,7 @@ describe('plugin-meetings', () => {
|
|
|
60
60
|
meeting.annotaion = {cleanUp: sinon.stub()};
|
|
61
61
|
meeting.getWebexObject = sinon.stub().returns(webex);
|
|
62
62
|
meeting.simultaneousInterpretation = {cleanUp: sinon.stub()};
|
|
63
|
+
meeting.locusInfo = {cleanUp: sinon.stub()};
|
|
63
64
|
meeting.trigger = sinon.stub();
|
|
64
65
|
meeting.webex = webex;
|
|
65
66
|
meeting.webex.internal.newMetrics.callDiagnosticMetrics =
|
|
@@ -89,6 +90,7 @@ describe('plugin-meetings', () => {
|
|
|
89
90
|
assert.calledOnceWithExactly(meeting.cleanupLLMConneciton, {throwOnError: false});
|
|
90
91
|
assert.calledOnce(meeting.breakouts.cleanUp);
|
|
91
92
|
assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
|
|
93
|
+
assert.calledOnce(meeting.locusInfo.cleanUp);
|
|
92
94
|
assert.calledOnce(webex.internal.device.meetingEnded);
|
|
93
95
|
assert.calledOnceWithExactly(
|
|
94
96
|
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId,
|
|
@@ -110,6 +112,7 @@ describe('plugin-meetings', () => {
|
|
|
110
112
|
assert.notCalled(meeting.cleanupLLMConneciton);
|
|
111
113
|
assert.calledOnce(meeting.breakouts.cleanUp);
|
|
112
114
|
assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
|
|
115
|
+
assert.calledOnce(meeting.locusInfo.cleanUp);
|
|
113
116
|
assert.calledOnce(webex.internal.device.meetingEnded);
|
|
114
117
|
assert.calledOnceWithExactly(
|
|
115
118
|
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId,
|
|
@@ -130,6 +133,7 @@ describe('plugin-meetings', () => {
|
|
|
130
133
|
assert.notCalled(meeting.cleanupLLMConneciton);
|
|
131
134
|
assert.calledOnce(meeting.breakouts.cleanUp);
|
|
132
135
|
assert.calledOnce(meeting.simultaneousInterpretation.cleanUp);
|
|
136
|
+
assert.calledOnce(meeting.locusInfo.cleanUp);
|
|
133
137
|
assert.calledOnce(webex.internal.device.meetingEnded);
|
|
134
138
|
assert.calledOnceWithExactly(
|
|
135
139
|
meeting.webex.internal.newMetrics.callDiagnosticMetrics.clearEventLimitsForCorrelationId,
|