@webex/plugin-meetings 3.12.0-next.3 → 3.12.0-next.31
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 +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/constants.js +3 -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 +550 -346
- 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 +222 -61
- package/dist/locus-info/index.js.map +1 -1
- package/dist/meeting/index.js +372 -292
- 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 +146 -62
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +39 -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/metrics/constants.js +5 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +116 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/types/constants.d.ts +1 -0
- 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 +38 -5
- package/dist/types/meeting/index.d.ts +11 -0
- 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/types/metrics/constants.d.ts +4 -0
- package/dist/types/multistream/sendSlotManager.d.ts +23 -1
- package/dist/webinar/index.js +301 -226
- package/dist/webinar/index.js.map +1 -1
- package/package.json +16 -16
- package/src/constants.ts +1 -0
- 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 +273 -154
- package/src/hashTree/utils.ts +17 -0
- package/src/interceptors/locusRetry.ts +25 -4
- package/src/locus-info/index.ts +233 -79
- package/src/meeting/index.ts +98 -11
- package/src/meeting/util.ts +1 -0
- package/src/meetings/index.ts +58 -34
- package/src/meetings/util.ts +44 -1
- package/src/member/index.ts +10 -0
- package/src/member/types.ts +1 -0
- package/src/member/util.ts +3 -0
- package/src/metrics/constants.ts +5 -0
- package/src/multistream/sendSlotManager.ts +97 -3
- package/src/webinar/index.ts +75 -1
- 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 +839 -37
- 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 +262 -64
- package/test/unit/spec/meeting/index.js +54 -36
- package/test/unit/spec/meeting/utils.js +4 -0
- package/test/unit/spec/meetings/index.js +190 -8
- package/test/unit/spec/meetings/utils.js +124 -0
- package/test/unit/spec/member/index.js +7 -0
- package/test/unit/spec/member/util.js +24 -0
- package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
- package/test/unit/spec/webinar/index.ts +60 -0
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import {
|
|
39
39
|
ConnectionState,
|
|
40
40
|
MediaConnectionEventNames,
|
|
41
|
+
MediaCodecMimeType,
|
|
41
42
|
StatsAnalyzerEventNames,
|
|
42
43
|
StatsMonitorEventNames,
|
|
43
44
|
Errors,
|
|
@@ -1981,11 +1982,12 @@ describe('plugin-meetings', () => {
|
|
|
1981
1982
|
describe('#handleLLMOnline', () => {
|
|
1982
1983
|
beforeEach(() => {
|
|
1983
1984
|
webex.internal.llm.off = sinon.stub();
|
|
1985
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
1986
|
+
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
1984
1987
|
});
|
|
1985
1988
|
|
|
1986
|
-
it('
|
|
1989
|
+
it('emits transcription connected events', () => {
|
|
1987
1990
|
meeting.handleLLMOnline();
|
|
1988
|
-
assert.calledOnceWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
1989
1991
|
assert.calledWith(
|
|
1990
1992
|
TriggerProxy.trigger,
|
|
1991
1993
|
sinon.match.instanceOf(Meeting),
|
|
@@ -1996,6 +1998,24 @@ describe('plugin-meetings', () => {
|
|
|
1996
1998
|
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
|
|
1997
1999
|
);
|
|
1998
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
|
+
});
|
|
1999
2019
|
});
|
|
2000
2020
|
|
|
2001
2021
|
describe('#join', () => {
|
|
@@ -2015,6 +2035,7 @@ describe('plugin-meetings', () => {
|
|
|
2015
2035
|
it('should have #join', () => {
|
|
2016
2036
|
assert.exists(meeting.join);
|
|
2017
2037
|
});
|
|
2038
|
+
|
|
2018
2039
|
beforeEach(() => {
|
|
2019
2040
|
setCorrelationIdSpy = sinon.spy(meeting, 'setCorrelationId');
|
|
2020
2041
|
meeting.setLocus = sinon.stub().returns(true);
|
|
@@ -2168,7 +2189,6 @@ describe('plugin-meetings', () => {
|
|
|
2168
2189
|
await meeting.join().catch(() => {
|
|
2169
2190
|
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
2170
2191
|
|
|
2171
|
-
// Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
|
|
2172
2192
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
2173
2193
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2174
2194
|
name: 'client.call.initiated',
|
|
@@ -2200,6 +2220,7 @@ describe('plugin-meetings', () => {
|
|
|
2200
2220
|
});
|
|
2201
2221
|
});
|
|
2202
2222
|
});
|
|
2223
|
+
|
|
2203
2224
|
describe('lmm, transcription & permissionTokenRefresh decoupling', () => {
|
|
2204
2225
|
beforeEach(() => {
|
|
2205
2226
|
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
|
|
@@ -2270,7 +2291,6 @@ describe('plugin-meetings', () => {
|
|
|
2270
2291
|
const locusInfoParseStub = sinon.stub(meeting.locusInfo, 'parse');
|
|
2271
2292
|
sinon.stub(meeting, 'isJoined').returns(true);
|
|
2272
2293
|
|
|
2273
|
-
// Set up llm.on stub to capture the registered listener when updateLLMConnection is called
|
|
2274
2294
|
let locusLLMEventListener;
|
|
2275
2295
|
meeting.webex.internal.llm.on = sinon.stub().callsFake((eventName, callback) => {
|
|
2276
2296
|
if (eventName === 'event:locus.state_message') {
|
|
@@ -2279,16 +2299,12 @@ describe('plugin-meetings', () => {
|
|
|
2279
2299
|
});
|
|
2280
2300
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
2281
2301
|
|
|
2282
|
-
// we need the real meeting.updateLLMConnection not the mock
|
|
2283
2302
|
meeting.updateLLMConnection.restore();
|
|
2284
2303
|
|
|
2285
|
-
// Call updateLLMConnection to register the listener
|
|
2286
2304
|
await meeting.updateLLMConnection();
|
|
2287
2305
|
|
|
2288
|
-
// Verify the listener was registered and we captured it
|
|
2289
2306
|
assert.isDefined(locusLLMEventListener, 'LLM event listener should be registered');
|
|
2290
2307
|
|
|
2291
|
-
// Now trigger the event
|
|
2292
2308
|
const eventData = {
|
|
2293
2309
|
eventType: 'locus.state_message',
|
|
2294
2310
|
stateElementsMessage: {
|
|
@@ -2308,13 +2324,10 @@ describe('plugin-meetings', () => {
|
|
|
2308
2324
|
sinon.stub(meeting.webex.internal.llm, 'hasEverConnected').value(true);
|
|
2309
2325
|
sinon.stub(meeting.webex.internal.llm, 'registerAndConnect').resolves({});
|
|
2310
2326
|
|
|
2311
|
-
// Restore the real updateLLMConnection
|
|
2312
2327
|
meeting.updateLLMConnection.restore();
|
|
2313
2328
|
|
|
2314
|
-
// Call updateLLMConnection to start the timer
|
|
2315
2329
|
await meeting.updateLLMConnection();
|
|
2316
2330
|
|
|
2317
|
-
// Fast forward time by 3 minutes
|
|
2318
2331
|
fakeClock.tick(3 * 60 * 1000);
|
|
2319
2332
|
|
|
2320
2333
|
assert.calledWith(
|
|
@@ -2339,18 +2352,14 @@ describe('plugin-meetings', () => {
|
|
|
2339
2352
|
.stub(meeting.webex.internal.llm, 'getDatachannelUrl')
|
|
2340
2353
|
.returns('https://datachannel1.example.com');
|
|
2341
2354
|
|
|
2342
|
-
// Restore the real updateLLMConnection
|
|
2343
2355
|
meeting.updateLLMConnection.restore();
|
|
2344
2356
|
|
|
2345
|
-
// First, connect LLM and start the timer
|
|
2346
2357
|
isJoinedStub.returns(true);
|
|
2347
2358
|
meeting.webex.internal.llm.isConnected.returns(false);
|
|
2348
2359
|
await meeting.updateLLMConnection();
|
|
2349
2360
|
|
|
2350
|
-
// Verify timer was started
|
|
2351
2361
|
assert.exists(meeting.llmHealthCheckTimer);
|
|
2352
2362
|
|
|
2353
|
-
// Now simulate that we're no longer joined
|
|
2354
2363
|
isJoinedStub.returns(false);
|
|
2355
2364
|
meeting.webex.internal.llm.isConnected.returns(true);
|
|
2356
2365
|
|
|
@@ -2358,10 +2367,8 @@ describe('plugin-meetings', () => {
|
|
|
2358
2367
|
|
|
2359
2368
|
assert.calledOnce(meeting.webex.internal.llm.disconnectLLM);
|
|
2360
2369
|
|
|
2361
|
-
// Verify the timer was cleared (should be undefined)
|
|
2362
2370
|
assert.isUndefined(meeting.llmHealthCheckTimer);
|
|
2363
2371
|
|
|
2364
|
-
// Fast forward time to ensure no metric is sent
|
|
2365
2372
|
Metrics.sendBehavioralMetric.resetHistory();
|
|
2366
2373
|
fakeClock.tick(3 * 60 * 1000);
|
|
2367
2374
|
|
|
@@ -2396,7 +2403,6 @@ describe('plugin-meetings', () => {
|
|
|
2396
2403
|
.stub()
|
|
2397
2404
|
.rejects(new CaptchaError('bad captcha'));
|
|
2398
2405
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2399
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil, 'joinMeetingOptions');
|
|
2400
2406
|
|
|
2401
2407
|
try {
|
|
2402
2408
|
await meeting.join();
|
|
@@ -2410,8 +2416,7 @@ describe('plugin-meetings', () => {
|
|
|
2410
2416
|
);
|
|
2411
2417
|
assert.instanceOf(error, CaptchaError);
|
|
2412
2418
|
assert.equal(error.message, 'bad captcha');
|
|
2413
|
-
|
|
2414
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2419
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2415
2420
|
}
|
|
2416
2421
|
});
|
|
2417
2422
|
|
|
@@ -2420,7 +2425,6 @@ describe('plugin-meetings', () => {
|
|
|
2420
2425
|
.stub()
|
|
2421
2426
|
.rejects(new PasswordError('bad password'));
|
|
2422
2427
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2423
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2424
2428
|
|
|
2425
2429
|
try {
|
|
2426
2430
|
await meeting.join();
|
|
@@ -2434,8 +2438,7 @@ describe('plugin-meetings', () => {
|
|
|
2434
2438
|
);
|
|
2435
2439
|
assert.instanceOf(error, PasswordError);
|
|
2436
2440
|
assert.equal(error.message, 'bad password');
|
|
2437
|
-
|
|
2438
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2441
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2439
2442
|
}
|
|
2440
2443
|
});
|
|
2441
2444
|
|
|
@@ -2444,7 +2447,6 @@ describe('plugin-meetings', () => {
|
|
|
2444
2447
|
.stub()
|
|
2445
2448
|
.rejects(new PermissionError('bad permission'));
|
|
2446
2449
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2447
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2448
2450
|
|
|
2449
2451
|
try {
|
|
2450
2452
|
await meeting.join();
|
|
@@ -2458,14 +2460,14 @@ describe('plugin-meetings', () => {
|
|
|
2458
2460
|
);
|
|
2459
2461
|
assert.instanceOf(error, PermissionError);
|
|
2460
2462
|
assert.equal(error.message, 'bad permission');
|
|
2461
|
-
|
|
2462
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2463
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2463
2464
|
}
|
|
2464
2465
|
});
|
|
2465
2466
|
});
|
|
2466
2467
|
});
|
|
2467
2468
|
});
|
|
2468
2469
|
|
|
2470
|
+
|
|
2469
2471
|
describe('#addMedia', () => {
|
|
2470
2472
|
const muteStateStub = {
|
|
2471
2473
|
handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
|
|
@@ -9214,8 +9216,8 @@ describe('plugin-meetings', () => {
|
|
|
9214
9216
|
const fakeMultistreamRoapMediaConnection = {
|
|
9215
9217
|
createSendSlot: () => {
|
|
9216
9218
|
return {
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
+
setCustomCodecParameters: sinon.stub().resolves(),
|
|
9220
|
+
markCustomCodecParametersForDeletion: sinon.stub().resolves(),
|
|
9219
9221
|
};
|
|
9220
9222
|
},
|
|
9221
9223
|
};
|
|
@@ -9238,27 +9240,29 @@ describe('plugin-meetings', () => {
|
|
|
9238
9240
|
}
|
|
9239
9241
|
);
|
|
9240
9242
|
|
|
9241
|
-
it('should set
|
|
9243
|
+
it('should set custom codec parameters when shouldEnableMusicMode is true', async () => {
|
|
9242
9244
|
await meeting.enableMusicMode(true);
|
|
9243
9245
|
assert.calledOnceWithExactly(
|
|
9244
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9246
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters,
|
|
9247
|
+
MediaCodecMimeType.OPUS,
|
|
9245
9248
|
{
|
|
9246
9249
|
maxaveragebitrate: '64000',
|
|
9247
9250
|
maxplaybackrate: '48000',
|
|
9248
9251
|
}
|
|
9249
9252
|
);
|
|
9250
9253
|
assert.notCalled(
|
|
9251
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9254
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion
|
|
9252
9255
|
);
|
|
9253
9256
|
});
|
|
9254
9257
|
|
|
9255
|
-
it('should
|
|
9258
|
+
it('should mark custom codec parameters for deletion when shouldEnableMusicMode is false', async () => {
|
|
9256
9259
|
await meeting.enableMusicMode(false);
|
|
9257
9260
|
assert.calledOnceWithExactly(
|
|
9258
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9261
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion,
|
|
9262
|
+
MediaCodecMimeType.OPUS,
|
|
9259
9263
|
['maxaveragebitrate', 'maxplaybackrate']
|
|
9260
9264
|
);
|
|
9261
|
-
assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9265
|
+
assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters);
|
|
9262
9266
|
});
|
|
9263
9267
|
});
|
|
9264
9268
|
|
|
@@ -10413,14 +10417,24 @@ describe('plugin-meetings', () => {
|
|
|
10413
10417
|
);
|
|
10414
10418
|
done();
|
|
10415
10419
|
});
|
|
10416
|
-
it('listens to the self admitted guest event', (
|
|
10420
|
+
it('listens to the self admitted guest event without blocking on token prefetch', async () => {
|
|
10417
10421
|
meeting.stopKeepAlive = sinon.stub();
|
|
10418
10422
|
meeting.updateLLMConnection = sinon.stub();
|
|
10423
|
+
let resolvePrefetch;
|
|
10424
|
+
|
|
10425
|
+
meeting.ensureDefaultDatachannelTokenAfterAdmit = sinon
|
|
10426
|
+
.stub()
|
|
10427
|
+
.returns(new Promise((resolve) => {
|
|
10428
|
+
resolvePrefetch = resolve;
|
|
10429
|
+
}));
|
|
10419
10430
|
meeting.rtcMetrics = {
|
|
10420
10431
|
sendNextMetrics: sinon.stub(),
|
|
10421
10432
|
};
|
|
10433
|
+
|
|
10422
10434
|
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
10435
|
+
|
|
10423
10436
|
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
10437
|
+
assert.calledOnceWithExactly(meeting.ensureDefaultDatachannelTokenAfterAdmit);
|
|
10424
10438
|
assert.calledThrice(TriggerProxy.trigger);
|
|
10425
10439
|
assert.calledWith(
|
|
10426
10440
|
TriggerProxy.trigger,
|
|
@@ -10439,7 +10453,11 @@ describe('plugin-meetings', () => {
|
|
|
10439
10453
|
correlation_id: meeting.correlationId,
|
|
10440
10454
|
}
|
|
10441
10455
|
);
|
|
10442
|
-
|
|
10456
|
+
|
|
10457
|
+
resolvePrefetch(false);
|
|
10458
|
+
await Promise.resolve();
|
|
10459
|
+
|
|
10460
|
+
assert.calledOnce(meeting.updateLLMConnection);
|
|
10443
10461
|
});
|
|
10444
10462
|
|
|
10445
10463
|
it('listens to the breakouts changed event', () => {
|
|
@@ -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,
|
|
@@ -91,6 +91,7 @@ describe('plugin-meetings', () => {
|
|
|
91
91
|
locusInfo = {
|
|
92
92
|
parse: sinon.stub().returns(true),
|
|
93
93
|
updateMainSessionLocusCache: sinon.stub(),
|
|
94
|
+
syncAllHashTreeDatasets: sinon.stub(),
|
|
94
95
|
};
|
|
95
96
|
webex = new MockWebex({
|
|
96
97
|
children: {
|
|
@@ -1285,10 +1286,10 @@ describe('plugin-meetings', () => {
|
|
|
1285
1286
|
assert.exists(result.dispose);
|
|
1286
1287
|
});
|
|
1287
1288
|
|
|
1288
|
-
it('creates noise reduction effect with
|
|
1289
|
+
it('creates noise reduction effect with OFMV model', async () => {
|
|
1289
1290
|
const result = await webex.meetings.createNoiseReductionEffect({
|
|
1290
1291
|
audioContext: {},
|
|
1291
|
-
model: '
|
|
1292
|
+
model: 'ofmv',
|
|
1292
1293
|
});
|
|
1293
1294
|
|
|
1294
1295
|
assert.exists(result);
|
|
@@ -1300,7 +1301,7 @@ describe('plugin-meetings', () => {
|
|
|
1300
1301
|
authToken: 'fake_token',
|
|
1301
1302
|
mode: 'WORKLET',
|
|
1302
1303
|
avoidSimd: false,
|
|
1303
|
-
model: '
|
|
1304
|
+
model: 'ofmv',
|
|
1304
1305
|
});
|
|
1305
1306
|
assert.exists(result.enable);
|
|
1306
1307
|
assert.exists(result.disable);
|
|
@@ -1391,7 +1392,7 @@ describe('plugin-meetings', () => {
|
|
|
1391
1392
|
it('should have #syncMeetings', () => {
|
|
1392
1393
|
assert.exists(webex.meetings.syncMeetings);
|
|
1393
1394
|
});
|
|
1394
|
-
it('should
|
|
1395
|
+
it('should skip getActiveMeetings but still call syncAllHashTreeDatasets if unverified guest', async () => {
|
|
1395
1396
|
webex.meetings.request.getActiveMeetings = sinon.stub().returns(
|
|
1396
1397
|
Promise.resolve({
|
|
1397
1398
|
loci: [
|
|
@@ -1404,13 +1405,23 @@ describe('plugin-meetings', () => {
|
|
|
1404
1405
|
webex.credentials.isUnverifiedGuest = true;
|
|
1405
1406
|
LoggerProxy.logger.info = sinon.stub();
|
|
1406
1407
|
|
|
1408
|
+
const mockLocusInfo = {
|
|
1409
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1410
|
+
};
|
|
1411
|
+
webex.meetings.meetingCollection.getAll = sinon.stub().returns({
|
|
1412
|
+
meeting1: {locusInfo: mockLocusInfo},
|
|
1413
|
+
meeting2: {locusInfo: undefined},
|
|
1414
|
+
meeting3: {},
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1407
1417
|
await webex.meetings.syncMeetings();
|
|
1408
1418
|
|
|
1409
1419
|
assert.notCalled(webex.meetings.request.getActiveMeetings);
|
|
1410
1420
|
assert.calledWith(
|
|
1411
1421
|
LoggerProxy.logger.info,
|
|
1412
|
-
'Meetings:index#syncMeetings --> skipping
|
|
1422
|
+
'Meetings:index#syncMeetings --> user is unverified guest, skipping calling Locus for meeting sync'
|
|
1413
1423
|
);
|
|
1424
|
+
assert.calledOnce(mockLocusInfo.syncAllHashTreeDatasets);
|
|
1414
1425
|
});
|
|
1415
1426
|
describe('succesful requests', () => {
|
|
1416
1427
|
beforeEach(() => {
|
|
@@ -1429,6 +1440,9 @@ describe('plugin-meetings', () => {
|
|
|
1429
1440
|
webex.meetings.meetingCollection.getByKey = sinon.stub().returns({
|
|
1430
1441
|
locusInfo,
|
|
1431
1442
|
});
|
|
1443
|
+
webex.meetings.meetingCollection.getAll = sinon.stub().returns({
|
|
1444
|
+
meeting1: {locusInfo, locusUrl: url1},
|
|
1445
|
+
});
|
|
1432
1446
|
});
|
|
1433
1447
|
it('tests the sync meeting calls for existing meeting', async () => {
|
|
1434
1448
|
await webex.meetings.syncMeetings();
|
|
@@ -1436,6 +1450,7 @@ describe('plugin-meetings', () => {
|
|
|
1436
1450
|
assert.calledOnce(webex.meetings.meetingCollection.getByKey);
|
|
1437
1451
|
assert.calledOnce(locusInfo.parse);
|
|
1438
1452
|
assert.calledWith(webex.meetings.meetingCollection.getByKey, 'locusUrl', url1);
|
|
1453
|
+
assert.calledOnce(locusInfo.syncAllHashTreeDatasets);
|
|
1439
1454
|
});
|
|
1440
1455
|
});
|
|
1441
1456
|
describe('when meeting is not returned', () => {
|
|
@@ -1520,7 +1535,7 @@ describe('plugin-meetings', () => {
|
|
|
1520
1535
|
it('destroy any meeting that has no active locus url if keepOnlyLocusMeetings is not defined', async () => {
|
|
1521
1536
|
await webex.meetings.syncMeetings();
|
|
1522
1537
|
assert.calledOnce(webex.meetings.request.getActiveMeetings);
|
|
1523
|
-
assert.
|
|
1538
|
+
assert.calledTwice(webex.meetings.meetingCollection.getAll);
|
|
1524
1539
|
assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
|
|
1525
1540
|
assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting1);
|
|
1526
1541
|
assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting2);
|
|
@@ -1532,7 +1547,7 @@ describe('plugin-meetings', () => {
|
|
|
1532
1547
|
it('destroy any meeting that has no active locus url if keepOnlyLocusMeetings === true', async () => {
|
|
1533
1548
|
await webex.meetings.syncMeetings({keepOnlyLocusMeetings: true});
|
|
1534
1549
|
assert.calledOnce(webex.meetings.request.getActiveMeetings);
|
|
1535
|
-
assert.
|
|
1550
|
+
assert.calledTwice(webex.meetings.meetingCollection.getAll);
|
|
1536
1551
|
assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
|
|
1537
1552
|
assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting1);
|
|
1538
1553
|
assert.calledWith(destroySpy, meetingCollectionMeetings.otherNonLocusMeeting2);
|
|
@@ -1544,7 +1559,7 @@ describe('plugin-meetings', () => {
|
|
|
1544
1559
|
it('destroy any LOCUS meetings that have no active locus url if keepOnlyLocusMeetings === false', async () => {
|
|
1545
1560
|
await webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
|
|
1546
1561
|
assert.calledOnce(webex.meetings.request.getActiveMeetings);
|
|
1547
|
-
assert.
|
|
1562
|
+
assert.calledTwice(webex.meetings.meetingCollection.getAll);
|
|
1548
1563
|
assert.calledWith(destroySpy, meetingCollectionMeetings.noLongerValidLocusMeeting);
|
|
1549
1564
|
assert.callCount(destroySpy, 1);
|
|
1550
1565
|
|
|
@@ -1552,6 +1567,113 @@ describe('plugin-meetings', () => {
|
|
|
1552
1567
|
});
|
|
1553
1568
|
});
|
|
1554
1569
|
});
|
|
1570
|
+
|
|
1571
|
+
describe('when globalMeetingId preserves breakout meetings', () => {
|
|
1572
|
+
let destroySpy;
|
|
1573
|
+
let cleanUpSpy;
|
|
1574
|
+
|
|
1575
|
+
beforeEach(() => {
|
|
1576
|
+
destroySpy = sinon.spy(webex.meetings, 'destroy');
|
|
1577
|
+
cleanUpSpy = sinon.stub(MeetingUtil, 'cleanUp').returns(Promise.resolve());
|
|
1578
|
+
});
|
|
1579
|
+
|
|
1580
|
+
afterEach(() => {
|
|
1581
|
+
cleanUpSpy.restore();
|
|
1582
|
+
});
|
|
1583
|
+
|
|
1584
|
+
it('should not destroy a meeting whose globalMeetingId matches an active locus', async () => {
|
|
1585
|
+
const meetingCollectionMeetings = {
|
|
1586
|
+
breakoutMeeting: {
|
|
1587
|
+
locusUrl: 'breakout-url',
|
|
1588
|
+
locusInfo: {
|
|
1589
|
+
info: {globalMeetingId: 'gmid-123'},
|
|
1590
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1591
|
+
},
|
|
1592
|
+
sendCallAnalyzerMetrics: sinon.stub(),
|
|
1593
|
+
},
|
|
1594
|
+
};
|
|
1595
|
+
|
|
1596
|
+
webex.meetings.meetingCollection.getAll = sinon
|
|
1597
|
+
.stub()
|
|
1598
|
+
.returns(meetingCollectionMeetings);
|
|
1599
|
+
webex.meetings.request.getActiveMeetings = sinon.stub().resolves({
|
|
1600
|
+
loci: [{url: 'main-url', info: {globalMeetingId: 'gmid-123'}}],
|
|
1601
|
+
});
|
|
1602
|
+
|
|
1603
|
+
await webex.meetings.syncMeetings();
|
|
1604
|
+
|
|
1605
|
+
assert.notCalled(destroySpy);
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
it('should destroy a meeting whose globalMeetingId does NOT match any active locus', async () => {
|
|
1609
|
+
const meetingCollectionMeetings = {
|
|
1610
|
+
breakoutMeeting: {
|
|
1611
|
+
locusUrl: 'breakout-url',
|
|
1612
|
+
locusInfo: {
|
|
1613
|
+
info: {globalMeetingId: 'gmid-other'},
|
|
1614
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1615
|
+
},
|
|
1616
|
+
sendCallAnalyzerMetrics: sinon.stub(),
|
|
1617
|
+
},
|
|
1618
|
+
};
|
|
1619
|
+
|
|
1620
|
+
webex.meetings.meetingCollection.getAll = sinon
|
|
1621
|
+
.stub()
|
|
1622
|
+
.returns(meetingCollectionMeetings);
|
|
1623
|
+
webex.meetings.request.getActiveMeetings = sinon.stub().resolves({
|
|
1624
|
+
loci: [{url: 'main-url', info: {globalMeetingId: 'gmid-123'}}],
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
await webex.meetings.syncMeetings();
|
|
1628
|
+
|
|
1629
|
+
assert.calledOnce(destroySpy);
|
|
1630
|
+
assert.calledWith(destroySpy, meetingCollectionMeetings.breakoutMeeting);
|
|
1631
|
+
});
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
describe('syncAllHashTreeDatasets in syncMeetings', () => {
|
|
1635
|
+
it('should call syncAllHashTreeDatasets for multiple meetings, skipping those without locusInfo', async () => {
|
|
1636
|
+
const mockLocusInfo1 = {
|
|
1637
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1638
|
+
};
|
|
1639
|
+
const mockLocusInfo2 = {
|
|
1640
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1641
|
+
};
|
|
1642
|
+
|
|
1643
|
+
webex.meetings.request.getActiveMeetings = sinon.stub().resolves({loci: []});
|
|
1644
|
+
webex.meetings.meetingCollection.getAll = sinon.stub().returns({
|
|
1645
|
+
meeting1: {locusInfo: mockLocusInfo1},
|
|
1646
|
+
meeting2: {locusInfo: undefined},
|
|
1647
|
+
meeting3: {locusInfo: mockLocusInfo2},
|
|
1648
|
+
meeting4: {},
|
|
1649
|
+
});
|
|
1650
|
+
|
|
1651
|
+
await webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
|
|
1652
|
+
|
|
1653
|
+
assert.calledOnce(mockLocusInfo1.syncAllHashTreeDatasets);
|
|
1654
|
+
assert.calledOnce(mockLocusInfo2.syncAllHashTreeDatasets);
|
|
1655
|
+
});
|
|
1656
|
+
|
|
1657
|
+
it('should not call syncAllHashTreeDatasets when getActiveMeetings throws an error', async () => {
|
|
1658
|
+
const mockLocusInfo = {
|
|
1659
|
+
syncAllHashTreeDatasets: sinon.stub().resolves(),
|
|
1660
|
+
};
|
|
1661
|
+
|
|
1662
|
+
webex.meetings.request.getActiveMeetings = sinon.stub().rejects(new Error('network error'));
|
|
1663
|
+
webex.meetings.meetingCollection.getAll = sinon.stub().returns({
|
|
1664
|
+
meeting1: {locusInfo: mockLocusInfo},
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
try {
|
|
1668
|
+
await webex.meetings.syncMeetings();
|
|
1669
|
+
assert.fail('should have thrown');
|
|
1670
|
+
} catch (err) {
|
|
1671
|
+
assert.equal(err.message, 'network error');
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
assert.notCalled(mockLocusInfo.syncAllHashTreeDatasets);
|
|
1675
|
+
});
|
|
1676
|
+
});
|
|
1555
1677
|
});
|
|
1556
1678
|
describe('#fetchStaticMeetingLink', () => {
|
|
1557
1679
|
const conversationUrl = 'conv.fakeconversationurl.com';
|
|
@@ -2833,6 +2955,39 @@ describe('plugin-meetings', () => {
|
|
|
2833
2955
|
checkCreateMeetingWithNoMeetingInfo(true, true);
|
|
2834
2956
|
});
|
|
2835
2957
|
|
|
2958
|
+
it('does not emit meeting:added when meeting is destroyed due to missing meeting info', async () => {
|
|
2959
|
+
// Make destroy actually remove the meeting from the collection
|
|
2960
|
+
// so that getMeetingByType returns null in the finally block
|
|
2961
|
+
webex.meetings.destroy = sinon.stub().callsFake((meeting) => {
|
|
2962
|
+
webex.meetings.meetingCollection.delete(meeting.id);
|
|
2963
|
+
});
|
|
2964
|
+
|
|
2965
|
+
try {
|
|
2966
|
+
await webex.meetings.createMeeting(
|
|
2967
|
+
'test destination',
|
|
2968
|
+
'test type',
|
|
2969
|
+
undefined,
|
|
2970
|
+
undefined,
|
|
2971
|
+
undefined,
|
|
2972
|
+
true
|
|
2973
|
+
);
|
|
2974
|
+
assert.fail('should have thrown NoMeetingInfoError');
|
|
2975
|
+
} catch (err) {
|
|
2976
|
+
assert.instanceOf(err, NoMeetingInfoError);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
assert.calledOnce(webex.meetings.destroy);
|
|
2980
|
+
|
|
2981
|
+
// meeting:added should NOT have been triggered since the meeting was destroyed
|
|
2982
|
+
assert.neverCalledWith(
|
|
2983
|
+
TriggerProxy.trigger,
|
|
2984
|
+
sinon.match.any,
|
|
2985
|
+
sinon.match({function: 'createMeeting'}),
|
|
2986
|
+
'meeting:added',
|
|
2987
|
+
sinon.match.any
|
|
2988
|
+
);
|
|
2989
|
+
});
|
|
2990
|
+
|
|
2836
2991
|
it('creates the meeting avoiding meeting info fetch by passing type as DESTINATION_TYPE.ONE_ON_ONE_CALL', async () => {
|
|
2837
2992
|
const meeting = await webex.meetings.createMeeting(
|
|
2838
2993
|
'test destination',
|
|
@@ -3426,6 +3581,21 @@ describe('plugin-meetings', () => {
|
|
|
3426
3581
|
'Meetings:index#isNeedHandleMainLocus --> self device left&moved in main locus with self joined status, not need to handle'
|
|
3427
3582
|
);
|
|
3428
3583
|
});
|
|
3584
|
+
|
|
3585
|
+
it('check breakout ended with self removed, return false', () => {
|
|
3586
|
+
webex.meetings.meetingCollection.getActiveBreakoutLocus = sinon.stub().returns(null);
|
|
3587
|
+
newLocus.self.state = 'LEFT';
|
|
3588
|
+
newLocus.self.reason = 'OTHER';
|
|
3589
|
+
newLocus.self.removed = true;
|
|
3590
|
+
newLocus.fullState = {state: 'INACTIVE', endMeetingReason: 'BREAKOUT_ENDED'};
|
|
3591
|
+
LoggerProxy.logger.log = sinon.stub();
|
|
3592
|
+
const result = webex.meetings.isNeedHandleMainLocus(meeting, newLocus);
|
|
3593
|
+
assert.equal(result, false);
|
|
3594
|
+
assert.calledWith(
|
|
3595
|
+
LoggerProxy.logger.log,
|
|
3596
|
+
'Meetings:index#isNeedHandleMainLocus --> self moved main locus with self removed status or with device resource moved, not need to handle'
|
|
3597
|
+
);
|
|
3598
|
+
});
|
|
3429
3599
|
});
|
|
3430
3600
|
|
|
3431
3601
|
describe('#isNeedHandleLocusDTO', () => {
|
|
@@ -3486,6 +3656,18 @@ describe('plugin-meetings', () => {
|
|
|
3486
3656
|
const result = webex.meetings.isNeedHandleLocusDTO(meeting, newLocus);
|
|
3487
3657
|
assert.equal(result, false);
|
|
3488
3658
|
});
|
|
3659
|
+
it('breakout session with breakout ended, return false', () => {
|
|
3660
|
+
newLocus.controls.breakout = {
|
|
3661
|
+
sessionType: 'BREAKOUT',
|
|
3662
|
+
};
|
|
3663
|
+
newLocus.self.state = 'LEFT';
|
|
3664
|
+
newLocus.self.reason = 'OTHER';
|
|
3665
|
+
newLocus.self.devices = [];
|
|
3666
|
+
newLocus.fullState = {state: 'INACTIVE', endMeetingReason: 'BREAKOUT_ENDED'};
|
|
3667
|
+
LoggerProxy.logger.log = sinon.stub();
|
|
3668
|
+
const result = webex.meetings.isNeedHandleLocusDTO(meeting, newLocus);
|
|
3669
|
+
assert.equal(result, false);
|
|
3670
|
+
});
|
|
3489
3671
|
it('moved to lobby, return true', () => {
|
|
3490
3672
|
newLocus.controls.breakout = {
|
|
3491
3673
|
sessionType: 'MAIN',
|