@webex/plugin-meetings 3.11.0 → 3.12.0-next.10
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 +184 -0
- package/dist/aiEnableRequest/index.js.map +1 -0
- package/dist/aiEnableRequest/utils.js +36 -0
- package/dist/aiEnableRequest/utils.js.map +1 -0
- package/dist/annotation/index.js +14 -5
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/config.js +7 -2
- package/dist/config.js.map +1 -1
- package/dist/constants.js +28 -6
- package/dist/constants.js.map +1 -1
- package/dist/hashTree/constants.js +13 -2
- package/dist/hashTree/constants.js.map +1 -1
- package/dist/hashTree/hashTree.js +18 -0
- package/dist/hashTree/hashTree.js.map +1 -1
- package/dist/hashTree/hashTreeParser.js +869 -420
- package/dist/hashTree/hashTreeParser.js.map +1 -1
- package/dist/hashTree/types.js +4 -2
- package/dist/hashTree/types.js.map +1 -1
- package/dist/hashTree/utils.js +32 -0
- package/dist/hashTree/utils.js.map +1 -1
- package/dist/index.js +11 -2
- package/dist/index.js.map +1 -1
- package/dist/interceptors/constant.js +12 -0
- package/dist/interceptors/constant.js.map +1 -0
- package/dist/interceptors/dataChannelAuthToken.js +290 -0
- package/dist/interceptors/dataChannelAuthToken.js.map +1 -0
- package/dist/interceptors/index.js +7 -0
- package/dist/interceptors/index.js.map +1 -1
- package/dist/interceptors/utils.js +27 -0
- package/dist/interceptors/utils.js.map +1 -0
- package/dist/interpretation/index.js +2 -2
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.js +5 -3
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +522 -131
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +1 -0
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/locus-info/types.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +57 -1
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/properties.js +4 -2
- package/dist/media/properties.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +7 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +1304 -928
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +50 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +133 -3
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +117 -48
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +6 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +9 -60
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +11 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +116 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/index.js +18 -10
- package/dist/reachability/index.js.map +1 -1
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +0 -1
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/types/aiEnableRequest/index.d.ts +5 -0
- package/dist/types/aiEnableRequest/utils.d.ts +2 -0
- package/dist/types/config.d.ts +4 -0
- package/dist/types/constants.d.ts +23 -1
- package/dist/types/hashTree/constants.d.ts +2 -0
- package/dist/types/hashTree/hashTree.d.ts +7 -0
- package/dist/types/hashTree/hashTreeParser.d.ts +122 -14
- package/dist/types/hashTree/types.d.ts +3 -0
- package/dist/types/hashTree/utils.d.ts +17 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/interceptors/constant.d.ts +5 -0
- package/dist/types/interceptors/dataChannelAuthToken.d.ts +43 -0
- package/dist/types/interceptors/index.d.ts +2 -1
- package/dist/types/interceptors/utils.d.ts +1 -0
- package/dist/types/locus-info/index.d.ts +60 -8
- package/dist/types/locus-info/types.d.ts +7 -0
- package/dist/types/media/MediaConnectionAwaiter.d.ts +10 -1
- package/dist/types/media/properties.d.ts +2 -1
- package/dist/types/meeting/in-meeting-actions.d.ts +6 -0
- package/dist/types/meeting/index.d.ts +72 -7
- package/dist/types/meeting/request.d.ts +16 -1
- package/dist/types/meeting/request.type.d.ts +5 -0
- package/dist/types/meeting/util.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +4 -2
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- package/dist/types/metrics/constants.d.ts +5 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
- package/dist/types/multistream/sendSlotManager.d.ts +23 -1
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/types/webinar/utils.d.ts +6 -0
- package/dist/webinar/index.js +438 -163
- package/dist/webinar/index.js.map +1 -1
- package/dist/webinar/utils.js +25 -0
- package/dist/webinar/utils.js.map +1 -0
- package/package.json +24 -23
- package/src/aiEnableRequest/README.md +84 -0
- package/src/aiEnableRequest/index.ts +170 -0
- package/src/aiEnableRequest/utils.ts +25 -0
- package/src/annotation/index.ts +27 -7
- package/src/config.ts +4 -0
- package/src/constants.ts +29 -1
- package/src/hashTree/constants.ts +10 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +764 -264
- package/src/hashTree/types.ts +4 -0
- package/src/hashTree/utils.ts +26 -0
- package/src/index.ts +8 -1
- package/src/interceptors/constant.ts +6 -0
- package/src/interceptors/dataChannelAuthToken.ts +170 -0
- package/src/interceptors/index.ts +2 -1
- package/src/interceptors/utils.ts +16 -0
- package/src/interpretation/index.ts +2 -2
- package/src/locus-info/controlsUtils.ts +11 -0
- package/src/locus-info/index.ts +579 -113
- package/src/locus-info/selfUtils.ts +1 -0
- package/src/locus-info/types.ts +8 -0
- package/src/media/MediaConnectionAwaiter.ts +41 -1
- package/src/media/properties.ts +3 -1
- package/src/meeting/in-meeting-actions.ts +12 -0
- package/src/meeting/index.ts +389 -87
- package/src/meeting/request.ts +42 -0
- package/src/meeting/request.type.ts +6 -0
- package/src/meeting/util.ts +160 -2
- package/src/meetings/index.ts +157 -44
- package/src/member/index.ts +10 -0
- package/src/member/util.ts +12 -0
- package/src/metrics/constants.ts +6 -0
- package/src/multistream/mediaRequestManager.ts +4 -54
- package/src/multistream/remoteMediaManager.ts +13 -0
- package/src/multistream/sendSlotManager.ts +97 -3
- package/src/reachability/index.ts +9 -0
- package/src/reactions/reactions.type.ts +1 -0
- package/src/reconnection-manager/index.ts +0 -1
- package/src/webinar/index.ts +265 -6
- package/src/webinar/utils.ts +16 -0
- package/test/unit/spec/aiEnableRequest/index.ts +981 -0
- package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
- package/test/unit/spec/annotation/index.ts +69 -7
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +2469 -195
- package/test/unit/spec/hashTree/utils.ts +88 -1
- package/test/unit/spec/interceptors/dataChannelAuthToken.ts +210 -0
- package/test/unit/spec/interceptors/utils.ts +75 -0
- package/test/unit/spec/locus-info/controlsUtils.js +29 -0
- package/test/unit/spec/locus-info/index.js +1134 -55
- package/test/unit/spec/media/MediaConnectionAwaiter.ts +41 -1
- package/test/unit/spec/media/properties.ts +12 -3
- package/test/unit/spec/meeting/in-meeting-actions.ts +8 -2
- package/test/unit/spec/meeting/index.js +884 -152
- package/test/unit/spec/meeting/request.js +70 -0
- package/test/unit/spec/meeting/utils.js +438 -26
- package/test/unit/spec/meetings/index.js +653 -32
- package/test/unit/spec/member/index.js +28 -4
- package/test/unit/spec/member/util.js +65 -27
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
- package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
- package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
- package/test/unit/spec/reachability/index.ts +23 -0
- package/test/unit/spec/reconnection-manager/index.js +4 -8
- package/test/unit/spec/webinar/index.ts +534 -37
- package/test/unit/spec/webinar/utils.ts +39 -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,
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
MediaType,
|
|
47
48
|
} from '@webex/internal-media-core';
|
|
48
49
|
import {LocalStreamEventNames} from '@webex/media-helpers';
|
|
50
|
+
import {CapabilityState, WebCapabilities} from '@webex/web-capabilities';
|
|
49
51
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
50
52
|
import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
|
|
51
53
|
import Meeting from '@webex/plugin-meetings/src/meeting';
|
|
@@ -81,6 +83,7 @@ import Mercury from '@webex/internal-plugin-mercury';
|
|
|
81
83
|
import Breakouts from '@webex/plugin-meetings/src/breakouts';
|
|
82
84
|
import SimultaneousInterpretation from '@webex/plugin-meetings/src/interpretation';
|
|
83
85
|
import Webinar from '@webex/plugin-meetings/src/webinar';
|
|
86
|
+
import AIEnableRequest from '@webex/plugin-meetings/src/aiEnableRequest';
|
|
84
87
|
import {REACTION_RELAY_TYPES} from '../../../../src/reactions/constants';
|
|
85
88
|
import locus from '../fixture/locus';
|
|
86
89
|
import {
|
|
@@ -122,7 +125,6 @@ import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
|
|
|
122
125
|
import {createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
|
|
123
126
|
import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
|
|
124
127
|
import {EventEmitter} from 'stream';
|
|
125
|
-
|
|
126
128
|
describe('plugin-meetings', () => {
|
|
127
129
|
const logger = {
|
|
128
130
|
info: () => {},
|
|
@@ -264,7 +266,9 @@ describe('plugin-meetings', () => {
|
|
|
264
266
|
stopReachability: sinon.stub(),
|
|
265
267
|
isSubnetReachable: sinon.stub().returns(true),
|
|
266
268
|
};
|
|
269
|
+
webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
|
|
267
270
|
webex.internal.llm.on = sinon.stub();
|
|
271
|
+
webex.internal.voicea.announce = sinon.stub();
|
|
268
272
|
webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
|
|
269
273
|
{},
|
|
270
274
|
{parent: webex}
|
|
@@ -375,6 +379,7 @@ describe('plugin-meetings', () => {
|
|
|
375
379
|
assert.instanceOf(meeting.breakouts, Breakouts);
|
|
376
380
|
assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
|
|
377
381
|
assert.instanceOf(meeting.webinar, Webinar);
|
|
382
|
+
assert.instanceOf(meeting.aiEnableRequest, AIEnableRequest);
|
|
378
383
|
});
|
|
379
384
|
|
|
380
385
|
it('should call the callback with the meeting that has id already set', () => {
|
|
@@ -734,8 +739,13 @@ describe('plugin-meetings', () => {
|
|
|
734
739
|
let handleTurnDiscoveryHttpResponseStub;
|
|
735
740
|
let abortTurnDiscoveryStub;
|
|
736
741
|
let addMediaInternalStub;
|
|
742
|
+
let supportsRTCPeerConnectionStub;
|
|
737
743
|
|
|
738
744
|
beforeEach(() => {
|
|
745
|
+
supportsRTCPeerConnectionStub = sinon
|
|
746
|
+
.stub(WebCapabilities, 'supportsRTCPeerConnection')
|
|
747
|
+
.returns(CapabilityState.CAPABLE);
|
|
748
|
+
|
|
739
749
|
meeting.join = sinon.stub().callsFake((joinOptions) => {
|
|
740
750
|
meeting.isMultistream = joinOptions.enableMultistream;
|
|
741
751
|
return Promise.resolve(fakeJoinResult);
|
|
@@ -1007,33 +1017,53 @@ describe('plugin-meetings', () => {
|
|
|
1007
1017
|
);
|
|
1008
1018
|
});
|
|
1009
1019
|
|
|
1010
|
-
it('should call leave() if addMediaInternal() fails ', async () => {
|
|
1020
|
+
it('should call leave() if addMediaInternal() fails with a browser media error (TypeError)', async () => {
|
|
1011
1021
|
const addMediaError = new Error('fake addMedia error');
|
|
1012
|
-
addMediaError.name = 'TypeError';
|
|
1022
|
+
addMediaError.name = 'TypeError'; // This makes it a browser media error
|
|
1013
1023
|
|
|
1014
|
-
const
|
|
1015
|
-
|
|
1016
|
-
body: {
|
|
1017
|
-
errorCode: 2729,
|
|
1018
|
-
message: 'fake addMedia error',
|
|
1019
|
-
name: 'TypeError'
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
};
|
|
1023
|
-
meeting.addMediaInternal.rejects(addMediaError);
|
|
1024
|
-
sinon.stub(meeting, 'leave').resolves();
|
|
1024
|
+
const leaveStub = sinon.stub(meeting, 'leave').resolves();
|
|
1025
|
+
meeting.addMediaInternal = sinon.stub().rejects(addMediaError);
|
|
1025
1026
|
|
|
1026
|
-
|
|
1027
|
+
// When a browser media error occurs, it gets transformed into a special structure
|
|
1028
|
+
const rejectedError = await assert.isRejected(
|
|
1027
1029
|
meeting.joinWithMedia({
|
|
1028
1030
|
joinOptions,
|
|
1029
1031
|
mediaOptions,
|
|
1030
|
-
})
|
|
1031
|
-
rejectError
|
|
1032
|
+
})
|
|
1032
1033
|
);
|
|
1033
1034
|
|
|
1035
|
+
// Verify the error was transformed with errorCode 2729
|
|
1036
|
+
assert.equal(rejectedError.error.body.errorCode, 2729);
|
|
1037
|
+
assert.equal(rejectedError.error.body.message, 'fake addMedia error');
|
|
1038
|
+
assert.equal(rejectedError.error.body.name, 'TypeError');
|
|
1039
|
+
|
|
1034
1040
|
assert.calledOnce(meeting.join);
|
|
1035
1041
|
assert.calledOnce(meeting.addMediaInternal);
|
|
1042
|
+
assert.calledOnce(leaveStub);
|
|
1043
|
+
assert.calledOnceWithExactly(leaveStub, {
|
|
1044
|
+
resourceId: undefined,
|
|
1045
|
+
reason: 'joinWithMedia failure',
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
// Browser media errors don't retry, so behavioral metric is sent only once
|
|
1049
|
+
// NOTE: The error gets transformed, so the metric receives undefined for message/stack/name
|
|
1050
|
+
// because they're now nested in error.body instead of at the top level
|
|
1036
1051
|
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
1052
|
+
assert.calledWith(
|
|
1053
|
+
Metrics.sendBehavioralMetric,
|
|
1054
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
1055
|
+
{
|
|
1056
|
+
correlation_id: meeting.correlationId,
|
|
1057
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1058
|
+
reason: undefined, // transformed error doesn't have .message at top level
|
|
1059
|
+
stack: undefined, // transformed error doesn't have .stack at top level
|
|
1060
|
+
leaveErrorReason: undefined,
|
|
1061
|
+
isRetry: false,
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
type: undefined, // transformed error doesn't have .name at top level
|
|
1065
|
+
}
|
|
1066
|
+
);
|
|
1037
1067
|
});
|
|
1038
1068
|
|
|
1039
1069
|
it('should not call leave() if addMediaInternal() fails the first time and succeeds the second time and should only call join() once', async () => {
|
|
@@ -1244,43 +1274,54 @@ describe('plugin-meetings', () => {
|
|
|
1244
1274
|
await assert.isRejected(result);
|
|
1245
1275
|
});
|
|
1246
1276
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1277
|
+
[
|
|
1278
|
+
{
|
|
1279
|
+
errorName: 'SdpOfferCreationError',
|
|
1280
|
+
description: 'if we fail to create the offer on first attempt',
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
errorName: 'WebrtcApiNotAvailableError',
|
|
1284
|
+
description: 'if RTCPeerConnection is not available',
|
|
1285
|
+
},
|
|
1286
|
+
].forEach(({errorName, description}) => {
|
|
1287
|
+
it(`should not attempt a retry ${description}`, async () => {
|
|
1288
|
+
const addMediaError = new Error('fake addMedia error');
|
|
1289
|
+
addMediaError.name = errorName;
|
|
1250
1290
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1291
|
+
meeting.addMediaInternal.rejects(addMediaError);
|
|
1292
|
+
sinon.stub(meeting, 'leave').resolves();
|
|
1253
1293
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1294
|
+
await assert.isRejected(
|
|
1295
|
+
meeting.joinWithMedia({
|
|
1296
|
+
joinOptions,
|
|
1297
|
+
mediaOptions,
|
|
1298
|
+
}),
|
|
1299
|
+
addMediaError
|
|
1300
|
+
);
|
|
1261
1301
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1302
|
+
// check that only 1 attempt was done
|
|
1303
|
+
assert.calledOnce(meeting.join);
|
|
1304
|
+
assert.calledOnce(meeting.addMediaInternal);
|
|
1305
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
1306
|
+
assert.calledWith(
|
|
1307
|
+
Metrics.sendBehavioralMetric.firstCall,
|
|
1308
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
1309
|
+
{
|
|
1310
|
+
correlation_id: meeting.correlationId,
|
|
1311
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1312
|
+
reason: addMediaError.message,
|
|
1313
|
+
stack: addMediaError.stack,
|
|
1314
|
+
leaveErrorReason: undefined,
|
|
1315
|
+
isRetry: false,
|
|
1316
|
+
},
|
|
1317
|
+
{
|
|
1318
|
+
type: addMediaError.name,
|
|
1319
|
+
}
|
|
1320
|
+
);
|
|
1321
|
+
assert.calledOnceWithExactly(meeting.leave, {
|
|
1322
|
+
resourceId: undefined,
|
|
1323
|
+
reason: 'joinWithMedia failure',
|
|
1324
|
+
});
|
|
1284
1325
|
});
|
|
1285
1326
|
});
|
|
1286
1327
|
|
|
@@ -1349,6 +1390,21 @@ describe('plugin-meetings', () => {
|
|
|
1349
1390
|
})
|
|
1350
1391
|
);
|
|
1351
1392
|
});
|
|
1393
|
+
|
|
1394
|
+
it('should throw immediately if RTCPeerConnection is not available', async () => {
|
|
1395
|
+
supportsRTCPeerConnectionStub.returns(CapabilityState.NOT_CAPABLE);
|
|
1396
|
+
|
|
1397
|
+
await assert.isRejected(
|
|
1398
|
+
meeting.joinWithMedia({
|
|
1399
|
+
joinOptions,
|
|
1400
|
+
mediaOptions,
|
|
1401
|
+
}),
|
|
1402
|
+
Errors.WebrtcApiNotAvailableError
|
|
1403
|
+
);
|
|
1404
|
+
|
|
1405
|
+
assert.notCalled(meeting.join);
|
|
1406
|
+
assert.notCalled(meeting.addMediaInternal);
|
|
1407
|
+
});
|
|
1352
1408
|
});
|
|
1353
1409
|
describe('#isTranscriptionSupported', () => {
|
|
1354
1410
|
it('should return false if the feature is not supported for the meeting', () => {
|
|
@@ -1497,6 +1553,22 @@ describe('plugin-meetings', () => {
|
|
|
1497
1553
|
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
1498
1554
|
);
|
|
1499
1555
|
});
|
|
1556
|
+
|
|
1557
|
+
it('should stop listening to voicea events even when transcription is undefined', () => {
|
|
1558
|
+
meeting.transcription = undefined;
|
|
1559
|
+
meeting.stopTranscription();
|
|
1560
|
+
assert.equal(webex.internal.voicea.off.callCount, 4);
|
|
1561
|
+
assert.equal(meeting.areVoiceaEventsSetup, false);
|
|
1562
|
+
assert.calledWith(
|
|
1563
|
+
TriggerProxy.trigger,
|
|
1564
|
+
sinon.match.instanceOf(Meeting),
|
|
1565
|
+
{
|
|
1566
|
+
file: 'meeting/index',
|
|
1567
|
+
function: 'triggerStopReceivingTranscriptionEvent',
|
|
1568
|
+
},
|
|
1569
|
+
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
1570
|
+
);
|
|
1571
|
+
});
|
|
1500
1572
|
});
|
|
1501
1573
|
|
|
1502
1574
|
describe('#setCaptionLanguage', () => {
|
|
@@ -1858,16 +1930,64 @@ describe('plugin-meetings', () => {
|
|
|
1858
1930
|
fakeProcessedReaction
|
|
1859
1931
|
);
|
|
1860
1932
|
});
|
|
1933
|
+
|
|
1934
|
+
it('should process if participantId does not exist in membersCollection but has displayName in Webinar', () => {
|
|
1935
|
+
LoggerProxy.logger.warn = sinon.stub();
|
|
1936
|
+
meeting.isReactionsSupported = sinon.stub().returns(true);
|
|
1937
|
+
meeting.config.receiveReactions = true;
|
|
1938
|
+
meeting.locusInfo.info = {isWebinar: true};
|
|
1939
|
+
const fakeSendersName = 'Fake reactors name';
|
|
1940
|
+
const fakeReactionPayload = {
|
|
1941
|
+
type: 'fake_type',
|
|
1942
|
+
codepoints: 'fake_codepoints',
|
|
1943
|
+
shortcodes: 'fake_shortcodes',
|
|
1944
|
+
tone: {
|
|
1945
|
+
type: 'fake_tone_type',
|
|
1946
|
+
codepoints: 'fake_tone_codepoints',
|
|
1947
|
+
shortcodes: 'fake_tone_shortcodes',
|
|
1948
|
+
},
|
|
1949
|
+
};
|
|
1950
|
+
const fakeSenderPayload = {
|
|
1951
|
+
displayName: 'Fake reactors name',
|
|
1952
|
+
participantId: 'fake_participant_id',
|
|
1953
|
+
};
|
|
1954
|
+
const fakeProcessedReaction = {
|
|
1955
|
+
reaction: fakeReactionPayload,
|
|
1956
|
+
sender: {
|
|
1957
|
+
id: fakeSenderPayload.participantId,
|
|
1958
|
+
name: fakeSendersName,
|
|
1959
|
+
},
|
|
1960
|
+
};
|
|
1961
|
+
const fakeRelayEvent = {
|
|
1962
|
+
data: {
|
|
1963
|
+
relayType: REACTION_RELAY_TYPES.REACTION,
|
|
1964
|
+
reaction: fakeReactionPayload,
|
|
1965
|
+
sender: fakeSenderPayload,
|
|
1966
|
+
},
|
|
1967
|
+
};
|
|
1968
|
+
meeting.processRelayEvent(fakeRelayEvent);
|
|
1969
|
+
assert.calledWith(
|
|
1970
|
+
TriggerProxy.trigger,
|
|
1971
|
+
sinon.match.instanceOf(Meeting),
|
|
1972
|
+
{
|
|
1973
|
+
file: 'meeting/index',
|
|
1974
|
+
function: 'join',
|
|
1975
|
+
},
|
|
1976
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
1977
|
+
fakeProcessedReaction
|
|
1978
|
+
);
|
|
1979
|
+
});
|
|
1861
1980
|
});
|
|
1862
1981
|
|
|
1863
1982
|
describe('#handleLLMOnline', () => {
|
|
1864
1983
|
beforeEach(() => {
|
|
1865
1984
|
webex.internal.llm.off = sinon.stub();
|
|
1985
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
1986
|
+
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
1866
1987
|
});
|
|
1867
1988
|
|
|
1868
|
-
it('
|
|
1989
|
+
it('emits transcription connected events', () => {
|
|
1869
1990
|
meeting.handleLLMOnline();
|
|
1870
|
-
assert.calledOnceWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
1871
1991
|
assert.calledWith(
|
|
1872
1992
|
TriggerProxy.trigger,
|
|
1873
1993
|
sinon.match.instanceOf(Meeting),
|
|
@@ -1878,6 +1998,24 @@ describe('plugin-meetings', () => {
|
|
|
1878
1998
|
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
|
|
1879
1999
|
);
|
|
1880
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
|
+
});
|
|
1881
2019
|
});
|
|
1882
2020
|
|
|
1883
2021
|
describe('#join', () => {
|
|
@@ -1897,6 +2035,7 @@ describe('plugin-meetings', () => {
|
|
|
1897
2035
|
it('should have #join', () => {
|
|
1898
2036
|
assert.exists(meeting.join);
|
|
1899
2037
|
});
|
|
2038
|
+
|
|
1900
2039
|
beforeEach(() => {
|
|
1901
2040
|
setCorrelationIdSpy = sinon.spy(meeting, 'setCorrelationId');
|
|
1902
2041
|
meeting.setLocus = sinon.stub().returns(true);
|
|
@@ -2050,7 +2189,6 @@ describe('plugin-meetings', () => {
|
|
|
2050
2189
|
await meeting.join().catch(() => {
|
|
2051
2190
|
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
2052
2191
|
|
|
2053
|
-
// Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
|
|
2054
2192
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
2055
2193
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2056
2194
|
name: 'client.call.initiated',
|
|
@@ -2082,6 +2220,7 @@ describe('plugin-meetings', () => {
|
|
|
2082
2220
|
});
|
|
2083
2221
|
});
|
|
2084
2222
|
});
|
|
2223
|
+
|
|
2085
2224
|
describe('lmm, transcription & permissionTokenRefresh decoupling', () => {
|
|
2086
2225
|
beforeEach(() => {
|
|
2087
2226
|
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
|
|
@@ -2152,7 +2291,6 @@ describe('plugin-meetings', () => {
|
|
|
2152
2291
|
const locusInfoParseStub = sinon.stub(meeting.locusInfo, 'parse');
|
|
2153
2292
|
sinon.stub(meeting, 'isJoined').returns(true);
|
|
2154
2293
|
|
|
2155
|
-
// Set up llm.on stub to capture the registered listener when updateLLMConnection is called
|
|
2156
2294
|
let locusLLMEventListener;
|
|
2157
2295
|
meeting.webex.internal.llm.on = sinon.stub().callsFake((eventName, callback) => {
|
|
2158
2296
|
if (eventName === 'event:locus.state_message') {
|
|
@@ -2161,16 +2299,12 @@ describe('plugin-meetings', () => {
|
|
|
2161
2299
|
});
|
|
2162
2300
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
2163
2301
|
|
|
2164
|
-
// we need the real meeting.updateLLMConnection not the mock
|
|
2165
2302
|
meeting.updateLLMConnection.restore();
|
|
2166
2303
|
|
|
2167
|
-
// Call updateLLMConnection to register the listener
|
|
2168
2304
|
await meeting.updateLLMConnection();
|
|
2169
2305
|
|
|
2170
|
-
// Verify the listener was registered and we captured it
|
|
2171
2306
|
assert.isDefined(locusLLMEventListener, 'LLM event listener should be registered');
|
|
2172
2307
|
|
|
2173
|
-
// Now trigger the event
|
|
2174
2308
|
const eventData = {
|
|
2175
2309
|
eventType: 'locus.state_message',
|
|
2176
2310
|
stateElementsMessage: {
|
|
@@ -2190,13 +2324,10 @@ describe('plugin-meetings', () => {
|
|
|
2190
2324
|
sinon.stub(meeting.webex.internal.llm, 'hasEverConnected').value(true);
|
|
2191
2325
|
sinon.stub(meeting.webex.internal.llm, 'registerAndConnect').resolves({});
|
|
2192
2326
|
|
|
2193
|
-
// Restore the real updateLLMConnection
|
|
2194
2327
|
meeting.updateLLMConnection.restore();
|
|
2195
2328
|
|
|
2196
|
-
// Call updateLLMConnection to start the timer
|
|
2197
2329
|
await meeting.updateLLMConnection();
|
|
2198
2330
|
|
|
2199
|
-
// Fast forward time by 3 minutes
|
|
2200
2331
|
fakeClock.tick(3 * 60 * 1000);
|
|
2201
2332
|
|
|
2202
2333
|
assert.calledWith(
|
|
@@ -2221,18 +2352,14 @@ describe('plugin-meetings', () => {
|
|
|
2221
2352
|
.stub(meeting.webex.internal.llm, 'getDatachannelUrl')
|
|
2222
2353
|
.returns('https://datachannel1.example.com');
|
|
2223
2354
|
|
|
2224
|
-
// Restore the real updateLLMConnection
|
|
2225
2355
|
meeting.updateLLMConnection.restore();
|
|
2226
2356
|
|
|
2227
|
-
// First, connect LLM and start the timer
|
|
2228
2357
|
isJoinedStub.returns(true);
|
|
2229
2358
|
meeting.webex.internal.llm.isConnected.returns(false);
|
|
2230
2359
|
await meeting.updateLLMConnection();
|
|
2231
2360
|
|
|
2232
|
-
// Verify timer was started
|
|
2233
2361
|
assert.exists(meeting.llmHealthCheckTimer);
|
|
2234
2362
|
|
|
2235
|
-
// Now simulate that we're no longer joined
|
|
2236
2363
|
isJoinedStub.returns(false);
|
|
2237
2364
|
meeting.webex.internal.llm.isConnected.returns(true);
|
|
2238
2365
|
|
|
@@ -2240,10 +2367,8 @@ describe('plugin-meetings', () => {
|
|
|
2240
2367
|
|
|
2241
2368
|
assert.calledOnce(meeting.webex.internal.llm.disconnectLLM);
|
|
2242
2369
|
|
|
2243
|
-
// Verify the timer was cleared (should be undefined)
|
|
2244
2370
|
assert.isUndefined(meeting.llmHealthCheckTimer);
|
|
2245
2371
|
|
|
2246
|
-
// Fast forward time to ensure no metric is sent
|
|
2247
2372
|
Metrics.sendBehavioralMetric.resetHistory();
|
|
2248
2373
|
fakeClock.tick(3 * 60 * 1000);
|
|
2249
2374
|
|
|
@@ -2278,7 +2403,6 @@ describe('plugin-meetings', () => {
|
|
|
2278
2403
|
.stub()
|
|
2279
2404
|
.rejects(new CaptchaError('bad captcha'));
|
|
2280
2405
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2281
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil, 'joinMeetingOptions');
|
|
2282
2406
|
|
|
2283
2407
|
try {
|
|
2284
2408
|
await meeting.join();
|
|
@@ -2292,8 +2416,7 @@ describe('plugin-meetings', () => {
|
|
|
2292
2416
|
);
|
|
2293
2417
|
assert.instanceOf(error, CaptchaError);
|
|
2294
2418
|
assert.equal(error.message, 'bad captcha');
|
|
2295
|
-
|
|
2296
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2419
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2297
2420
|
}
|
|
2298
2421
|
});
|
|
2299
2422
|
|
|
@@ -2302,7 +2425,6 @@ describe('plugin-meetings', () => {
|
|
|
2302
2425
|
.stub()
|
|
2303
2426
|
.rejects(new PasswordError('bad password'));
|
|
2304
2427
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2305
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2306
2428
|
|
|
2307
2429
|
try {
|
|
2308
2430
|
await meeting.join();
|
|
@@ -2316,8 +2438,7 @@ describe('plugin-meetings', () => {
|
|
|
2316
2438
|
);
|
|
2317
2439
|
assert.instanceOf(error, PasswordError);
|
|
2318
2440
|
assert.equal(error.message, 'bad password');
|
|
2319
|
-
|
|
2320
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2441
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2321
2442
|
}
|
|
2322
2443
|
});
|
|
2323
2444
|
|
|
@@ -2326,7 +2447,6 @@ describe('plugin-meetings', () => {
|
|
|
2326
2447
|
.stub()
|
|
2327
2448
|
.rejects(new PermissionError('bad permission'));
|
|
2328
2449
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2329
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2330
2450
|
|
|
2331
2451
|
try {
|
|
2332
2452
|
await meeting.join();
|
|
@@ -2340,14 +2460,14 @@ describe('plugin-meetings', () => {
|
|
|
2340
2460
|
);
|
|
2341
2461
|
assert.instanceOf(error, PermissionError);
|
|
2342
2462
|
assert.equal(error.message, 'bad permission');
|
|
2343
|
-
|
|
2344
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2463
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2345
2464
|
}
|
|
2346
2465
|
});
|
|
2347
2466
|
});
|
|
2348
2467
|
});
|
|
2349
2468
|
});
|
|
2350
2469
|
|
|
2470
|
+
|
|
2351
2471
|
describe('#addMedia', () => {
|
|
2352
2472
|
const muteStateStub = {
|
|
2353
2473
|
handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
|
|
@@ -3004,6 +3124,111 @@ describe('plugin-meetings', () => {
|
|
|
3004
3124
|
checkWorking({allowMediaInLobby: true});
|
|
3005
3125
|
});
|
|
3006
3126
|
|
|
3127
|
+
const setupLobbyTest = () => {
|
|
3128
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
3129
|
+
.stub()
|
|
3130
|
+
.resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
|
3131
|
+
|
|
3132
|
+
meeting.meetingState = 'ACTIVE';
|
|
3133
|
+
meeting.locusInfo.parsedLocus = {self: {state: 'IDLE'}};
|
|
3134
|
+
meeting.isUserUnadmitted = true;
|
|
3135
|
+
|
|
3136
|
+
// Mock locusMediaRequest
|
|
3137
|
+
meeting.locusMediaRequest = {
|
|
3138
|
+
send: sinon.stub().resolves(),
|
|
3139
|
+
isConfluenceCreated: sinon.stub().returns(false),
|
|
3140
|
+
};
|
|
3141
|
+
|
|
3142
|
+
sinon.stub(RemoteMediaManagerModule, 'RemoteMediaManager').returns({
|
|
3143
|
+
start: sinon.stub().resolves(),
|
|
3144
|
+
on: sinon.stub(),
|
|
3145
|
+
logAllReceiveSlots: sinon.stub(),
|
|
3146
|
+
});
|
|
3147
|
+
|
|
3148
|
+
meeting.isMultistream = true;
|
|
3149
|
+
|
|
3150
|
+
const createFakeStream = (id) => ({
|
|
3151
|
+
on: sinon.stub(),
|
|
3152
|
+
off: sinon.stub(),
|
|
3153
|
+
userMuted: false,
|
|
3154
|
+
systemMuted: false,
|
|
3155
|
+
get muted() {
|
|
3156
|
+
return this.userMuted || this.systemMuted;
|
|
3157
|
+
},
|
|
3158
|
+
setUnmuteAllowed: sinon.stub(),
|
|
3159
|
+
setUserMuted: sinon.stub(),
|
|
3160
|
+
outputStream: {
|
|
3161
|
+
getTracks: () => [{id}],
|
|
3162
|
+
},
|
|
3163
|
+
getSettings: sinon.stub().returns({}),
|
|
3164
|
+
});
|
|
3165
|
+
|
|
3166
|
+
return {
|
|
3167
|
+
fakeMicrophoneStream: createFakeStream('fake mic'),
|
|
3168
|
+
fakeCameraStream: createFakeStream('fake camera'),
|
|
3169
|
+
};
|
|
3170
|
+
};
|
|
3171
|
+
|
|
3172
|
+
it('should not publish any local streams when in the lobby and allowPublishMediaInLobby is false', async () => {
|
|
3173
|
+
const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
|
|
3174
|
+
|
|
3175
|
+
const publishStreamStub = sinon.stub();
|
|
3176
|
+
fakeMediaConnection.createSendSlot = sinon.stub().returns({
|
|
3177
|
+
publishStream: publishStreamStub,
|
|
3178
|
+
unpublishStream: sinon.stub(),
|
|
3179
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3180
|
+
});
|
|
3181
|
+
|
|
3182
|
+
await meeting.addMedia({
|
|
3183
|
+
allowMediaInLobby: true,
|
|
3184
|
+
allowPublishMediaInLobby: false,
|
|
3185
|
+
audioEnabled: true,
|
|
3186
|
+
videoEnabled: true,
|
|
3187
|
+
localStreams: {
|
|
3188
|
+
microphone: fakeMicrophoneStream,
|
|
3189
|
+
camera: fakeCameraStream,
|
|
3190
|
+
},
|
|
3191
|
+
});
|
|
3192
|
+
|
|
3193
|
+
assert.notCalled(publishStreamStub);
|
|
3194
|
+
});
|
|
3195
|
+
|
|
3196
|
+
it('should publish local streams when in the lobby and allowPublishMediaInLobby is true', async () => {
|
|
3197
|
+
const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
|
|
3198
|
+
|
|
3199
|
+
const audioSlot = {
|
|
3200
|
+
publishStream: sinon.stub(),
|
|
3201
|
+
unpublishStream: sinon.stub(),
|
|
3202
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3203
|
+
};
|
|
3204
|
+
const videoSlot = {
|
|
3205
|
+
publishStream: sinon.stub(),
|
|
3206
|
+
unpublishStream: sinon.stub(),
|
|
3207
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3208
|
+
};
|
|
3209
|
+
|
|
3210
|
+
fakeMediaConnection.createSendSlot = sinon.stub().callsFake((mediaType) => {
|
|
3211
|
+
if (mediaType === 'AUDIO-MAIN') {
|
|
3212
|
+
return audioSlot;
|
|
3213
|
+
}
|
|
3214
|
+
return videoSlot;
|
|
3215
|
+
});
|
|
3216
|
+
|
|
3217
|
+
await meeting.addMedia({
|
|
3218
|
+
allowMediaInLobby: true,
|
|
3219
|
+
allowPublishMediaInLobby: true,
|
|
3220
|
+
audioEnabled: true,
|
|
3221
|
+
videoEnabled: true,
|
|
3222
|
+
localStreams: {
|
|
3223
|
+
microphone: fakeMicrophoneStream,
|
|
3224
|
+
camera: fakeCameraStream,
|
|
3225
|
+
},
|
|
3226
|
+
});
|
|
3227
|
+
|
|
3228
|
+
assert.calledOnceWithExactly(audioSlot.publishStream, fakeMicrophoneStream);
|
|
3229
|
+
assert.calledOnceWithExactly(videoSlot.publishStream, fakeCameraStream);
|
|
3230
|
+
});
|
|
3231
|
+
|
|
3007
3232
|
it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
|
|
3008
3233
|
const setIntervalOriginal = window.setInterval;
|
|
3009
3234
|
window.setInterval = sinon.stub().returns(1);
|
|
@@ -6194,7 +6419,10 @@ describe('plugin-meetings', () => {
|
|
|
6194
6419
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
6195
6420
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
6196
6421
|
meeting.logger.error = sinon.stub().returns(true);
|
|
6197
|
-
meeting.
|
|
6422
|
+
meeting.clearMeetingData = sinon.stub().callsFake(async () => {
|
|
6423
|
+
meeting.audio = null;
|
|
6424
|
+
meeting.video = null;
|
|
6425
|
+
});
|
|
6198
6426
|
webex.internal.voicea.off = sinon.stub().returns(true);
|
|
6199
6427
|
meeting.stopTranscription = sinon.stub();
|
|
6200
6428
|
meeting.transcription = {};
|
|
@@ -6221,9 +6449,7 @@ describe('plugin-meetings', () => {
|
|
|
6221
6449
|
assert.calledOnce(meeting.closePeerConnections);
|
|
6222
6450
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
|
6223
6451
|
assert.calledOnce(meeting.unsetPeerConnections);
|
|
6224
|
-
assert.calledOnce(meeting.
|
|
6225
|
-
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
6226
|
-
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
6452
|
+
assert.calledOnce(meeting.clearMeetingData);
|
|
6227
6453
|
});
|
|
6228
6454
|
|
|
6229
6455
|
it('should reset call diagnostic latencies correctly', async () => {
|
|
@@ -8224,7 +8450,10 @@ describe('plugin-meetings', () => {
|
|
|
8224
8450
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
8225
8451
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
8226
8452
|
meeting.logger.error = sinon.stub().returns(true);
|
|
8227
|
-
meeting.
|
|
8453
|
+
meeting.clearMeetingData = sinon.stub().callsFake(async () => {
|
|
8454
|
+
meeting.audio = null;
|
|
8455
|
+
meeting.video = null;
|
|
8456
|
+
});
|
|
8228
8457
|
meeting.transcription = {};
|
|
8229
8458
|
meeting.stopTranscription = sinon.stub();
|
|
8230
8459
|
|
|
@@ -8250,10 +8479,7 @@ describe('plugin-meetings', () => {
|
|
|
8250
8479
|
assert.calledOnce(meeting?.closePeerConnections);
|
|
8251
8480
|
assert.calledOnce(meeting?.unsetRemoteStreams);
|
|
8252
8481
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
8253
|
-
assert.calledOnce(meeting?.
|
|
8254
|
-
|
|
8255
|
-
assert.called(meeting.annotation.deregisterEvents);
|
|
8256
|
-
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
8482
|
+
assert.calledOnce(meeting?.clearMeetingData);
|
|
8257
8483
|
});
|
|
8258
8484
|
});
|
|
8259
8485
|
|
|
@@ -8990,8 +9216,8 @@ describe('plugin-meetings', () => {
|
|
|
8990
9216
|
const fakeMultistreamRoapMediaConnection = {
|
|
8991
9217
|
createSendSlot: () => {
|
|
8992
9218
|
return {
|
|
8993
|
-
|
|
8994
|
-
|
|
9219
|
+
setCustomCodecParameters: sinon.stub().resolves(),
|
|
9220
|
+
markCustomCodecParametersForDeletion: sinon.stub().resolves(),
|
|
8995
9221
|
};
|
|
8996
9222
|
},
|
|
8997
9223
|
};
|
|
@@ -9014,27 +9240,29 @@ describe('plugin-meetings', () => {
|
|
|
9014
9240
|
}
|
|
9015
9241
|
);
|
|
9016
9242
|
|
|
9017
|
-
it('should set
|
|
9243
|
+
it('should set custom codec parameters when shouldEnableMusicMode is true', async () => {
|
|
9018
9244
|
await meeting.enableMusicMode(true);
|
|
9019
9245
|
assert.calledOnceWithExactly(
|
|
9020
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9246
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters,
|
|
9247
|
+
MediaCodecMimeType.OPUS,
|
|
9021
9248
|
{
|
|
9022
9249
|
maxaveragebitrate: '64000',
|
|
9023
9250
|
maxplaybackrate: '48000',
|
|
9024
9251
|
}
|
|
9025
9252
|
);
|
|
9026
9253
|
assert.notCalled(
|
|
9027
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9254
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion
|
|
9028
9255
|
);
|
|
9029
9256
|
});
|
|
9030
9257
|
|
|
9031
|
-
it('should
|
|
9258
|
+
it('should mark custom codec parameters for deletion when shouldEnableMusicMode is false', async () => {
|
|
9032
9259
|
await meeting.enableMusicMode(false);
|
|
9033
9260
|
assert.calledOnceWithExactly(
|
|
9034
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9261
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion,
|
|
9262
|
+
MediaCodecMimeType.OPUS,
|
|
9035
9263
|
['maxaveragebitrate', 'maxplaybackrate']
|
|
9036
9264
|
);
|
|
9037
|
-
assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9265
|
+
assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters);
|
|
9038
9266
|
});
|
|
9039
9267
|
});
|
|
9040
9268
|
|
|
@@ -9125,7 +9353,10 @@ describe('plugin-meetings', () => {
|
|
|
9125
9353
|
|
|
9126
9354
|
// check that the right things were called by the callback
|
|
9127
9355
|
assert.calledOnceWithExactly(meeting.waitForRemoteSDPAnswer);
|
|
9128
|
-
assert.calledOnceWithExactly(
|
|
9356
|
+
assert.calledOnceWithExactly(
|
|
9357
|
+
meeting.mediaProperties.waitForMediaConnectionConnected,
|
|
9358
|
+
meeting.correlationId
|
|
9359
|
+
);
|
|
9129
9360
|
});
|
|
9130
9361
|
});
|
|
9131
9362
|
|
|
@@ -10186,14 +10417,24 @@ describe('plugin-meetings', () => {
|
|
|
10186
10417
|
);
|
|
10187
10418
|
done();
|
|
10188
10419
|
});
|
|
10189
|
-
it('listens to the self admitted guest event', (
|
|
10420
|
+
it('listens to the self admitted guest event without blocking on token prefetch', async () => {
|
|
10190
10421
|
meeting.stopKeepAlive = sinon.stub();
|
|
10191
10422
|
meeting.updateLLMConnection = sinon.stub();
|
|
10423
|
+
let resolvePrefetch;
|
|
10424
|
+
|
|
10425
|
+
meeting.ensureDefaultDatachannelTokenAfterAdmit = sinon
|
|
10426
|
+
.stub()
|
|
10427
|
+
.returns(new Promise((resolve) => {
|
|
10428
|
+
resolvePrefetch = resolve;
|
|
10429
|
+
}));
|
|
10192
10430
|
meeting.rtcMetrics = {
|
|
10193
10431
|
sendNextMetrics: sinon.stub(),
|
|
10194
10432
|
};
|
|
10433
|
+
|
|
10195
10434
|
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
10435
|
+
|
|
10196
10436
|
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
10437
|
+
assert.calledOnceWithExactly(meeting.ensureDefaultDatachannelTokenAfterAdmit);
|
|
10197
10438
|
assert.calledThrice(TriggerProxy.trigger);
|
|
10198
10439
|
assert.calledWith(
|
|
10199
10440
|
TriggerProxy.trigger,
|
|
@@ -10212,7 +10453,11 @@ describe('plugin-meetings', () => {
|
|
|
10212
10453
|
correlation_id: meeting.correlationId,
|
|
10213
10454
|
}
|
|
10214
10455
|
);
|
|
10215
|
-
|
|
10456
|
+
|
|
10457
|
+
resolvePrefetch(false);
|
|
10458
|
+
await Promise.resolve();
|
|
10459
|
+
|
|
10460
|
+
assert.calledOnce(meeting.updateLLMConnection);
|
|
10216
10461
|
});
|
|
10217
10462
|
|
|
10218
10463
|
it('listens to the breakouts changed event', () => {
|
|
@@ -10299,6 +10544,21 @@ describe('plugin-meetings', () => {
|
|
|
10299
10544
|
EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
|
|
10300
10545
|
);
|
|
10301
10546
|
});
|
|
10547
|
+
|
|
10548
|
+
it('listens to the self id changed event and updates aiEnableRequest', () => {
|
|
10549
|
+
meeting.aiEnableRequest = {
|
|
10550
|
+
selfParticipantIdUpdate: sinon.stub(),
|
|
10551
|
+
};
|
|
10552
|
+
|
|
10553
|
+
const payload = {selfId: 'participant-test-123'};
|
|
10554
|
+
|
|
10555
|
+
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ID_CHANGED', payload);
|
|
10556
|
+
|
|
10557
|
+
assert.calledOnceWithExactly(
|
|
10558
|
+
meeting.aiEnableRequest.selfParticipantIdUpdate,
|
|
10559
|
+
payload.selfId
|
|
10560
|
+
);
|
|
10561
|
+
});
|
|
10302
10562
|
});
|
|
10303
10563
|
|
|
10304
10564
|
describe('#setUpBreakoutsListener', () => {
|
|
@@ -10546,6 +10806,24 @@ describe('plugin-meetings', () => {
|
|
|
10546
10806
|
);
|
|
10547
10807
|
});
|
|
10548
10808
|
|
|
10809
|
+
it('listens to MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED', async () => {
|
|
10810
|
+
const aiSummaryNotification = {example: 'value'};
|
|
10811
|
+
|
|
10812
|
+
await meeting.locusInfo.emitScoped(
|
|
10813
|
+
{function: 'test', file: 'test'},
|
|
10814
|
+
LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
10815
|
+
{aiSummaryNotification}
|
|
10816
|
+
);
|
|
10817
|
+
|
|
10818
|
+
assert.calledWith(
|
|
10819
|
+
TriggerProxy.trigger,
|
|
10820
|
+
meeting,
|
|
10821
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
10822
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
10823
|
+
{aiSummaryNotification}
|
|
10824
|
+
);
|
|
10825
|
+
});
|
|
10826
|
+
|
|
10549
10827
|
it('listens to MEETING_CONTROLS_MEETING_FULL_UPDATED', async () => {
|
|
10550
10828
|
const state = {example: 'value'};
|
|
10551
10829
|
|
|
@@ -10818,6 +11096,9 @@ describe('plugin-meetings', () => {
|
|
|
10818
11096
|
meeting.simultaneousInterpretation = {
|
|
10819
11097
|
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
10820
11098
|
};
|
|
11099
|
+
meeting.aiEnableRequest = {
|
|
11100
|
+
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
11101
|
+
};
|
|
10821
11102
|
|
|
10822
11103
|
meeting.locusInfo.emit(
|
|
10823
11104
|
{function: 'test', file: 'test'},
|
|
@@ -10837,7 +11118,11 @@ describe('plugin-meetings', () => {
|
|
|
10837
11118
|
meeting.simultaneousInterpretation.approvalUrlUpdate,
|
|
10838
11119
|
newLocusServices.services.approval.url
|
|
10839
11120
|
);
|
|
10840
|
-
assert.
|
|
11121
|
+
assert.calledWith(
|
|
11122
|
+
meeting.aiEnableRequest.approvalUrlUpdate,
|
|
11123
|
+
newLocusServices.services.approval.url
|
|
11124
|
+
);
|
|
11125
|
+
assert.calledOnce(meeting.recordingController.setSessionId);
|
|
10841
11126
|
done();
|
|
10842
11127
|
});
|
|
10843
11128
|
});
|
|
@@ -11242,6 +11527,41 @@ describe('plugin-meetings', () => {
|
|
|
11242
11527
|
});
|
|
11243
11528
|
});
|
|
11244
11529
|
|
|
11530
|
+
describe('localConstraintsChangeHandler', () => {
|
|
11531
|
+
it('calls updatePreferredBitrateKbps when not multistream', () => {
|
|
11532
|
+
meeting.isMultistream = false;
|
|
11533
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
11534
|
+
updatePreferredBitrateKbps: sinon.stub(),
|
|
11535
|
+
};
|
|
11536
|
+
|
|
11537
|
+
meeting.localConstraintsChangeHandler();
|
|
11538
|
+
|
|
11539
|
+
assert.calledOnce(
|
|
11540
|
+
meeting.mediaProperties.webrtcMediaConnection.updatePreferredBitrateKbps
|
|
11541
|
+
);
|
|
11542
|
+
});
|
|
11543
|
+
|
|
11544
|
+
it('does not call updatePreferredBitrateKbps when multistream', () => {
|
|
11545
|
+
meeting.isMultistream = true;
|
|
11546
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
11547
|
+
updatePreferredBitrateKbps: sinon.stub(),
|
|
11548
|
+
};
|
|
11549
|
+
|
|
11550
|
+
meeting.localConstraintsChangeHandler();
|
|
11551
|
+
|
|
11552
|
+
assert.notCalled(
|
|
11553
|
+
meeting.mediaProperties.webrtcMediaConnection.updatePreferredBitrateKbps
|
|
11554
|
+
);
|
|
11555
|
+
});
|
|
11556
|
+
|
|
11557
|
+
it('does not throw when webrtcMediaConnection is undefined', () => {
|
|
11558
|
+
meeting.isMultistream = false;
|
|
11559
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
11560
|
+
|
|
11561
|
+
assert.doesNotThrow(() => meeting.localConstraintsChangeHandler());
|
|
11562
|
+
});
|
|
11563
|
+
});
|
|
11564
|
+
|
|
11245
11565
|
describe('#parseMeetingInfo', () => {
|
|
11246
11566
|
const checkParseMeetingInfo = (expectedInfoToParse) => {
|
|
11247
11567
|
assert.equal(meeting.conversationUrl, expectedInfoToParse.conversationUrl);
|
|
@@ -11621,6 +11941,7 @@ describe('plugin-meetings', () => {
|
|
|
11621
11941
|
let canUnsetDisallowUnmuteSpy;
|
|
11622
11942
|
let canUserRaiseHandSpy;
|
|
11623
11943
|
let bothLeaveAndEndMeetingAvailableSpy;
|
|
11944
|
+
let requireHostEndMeetingBeforeLeaveSpy;
|
|
11624
11945
|
let canUserLowerAllHandsSpy;
|
|
11625
11946
|
let canUserLowerSomeoneElsesHandSpy;
|
|
11626
11947
|
let waitingForOthersToJoinSpy;
|
|
@@ -11632,6 +11953,8 @@ describe('plugin-meetings', () => {
|
|
|
11632
11953
|
let canMoveToLobbySpy;
|
|
11633
11954
|
let isSpokenLanguageAutoDetectionEnabledSpy;
|
|
11634
11955
|
let showAutoEndMeetingWarningSpy;
|
|
11956
|
+
let canAttendeeRequestAiAssistantEnabledSpy;
|
|
11957
|
+
let attendeeRequestAiAssistantDeclinedAllSpy;
|
|
11635
11958
|
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
|
11636
11959
|
|
|
11637
11960
|
beforeEach(() => {
|
|
@@ -11652,6 +11975,10 @@ describe('plugin-meetings', () => {
|
|
|
11652
11975
|
MeetingUtil,
|
|
11653
11976
|
'bothLeaveAndEndMeetingAvailable'
|
|
11654
11977
|
);
|
|
11978
|
+
requireHostEndMeetingBeforeLeaveSpy = sinon.spy(
|
|
11979
|
+
MeetingUtil,
|
|
11980
|
+
'requireHostEndMeetingBeforeLeave'
|
|
11981
|
+
);
|
|
11655
11982
|
canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
|
|
11656
11983
|
waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
|
|
11657
11984
|
canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
|
|
@@ -11668,12 +11995,22 @@ describe('plugin-meetings', () => {
|
|
|
11668
11995
|
MeetingUtil,
|
|
11669
11996
|
'isSpokenLanguageAutoDetectionEnabled'
|
|
11670
11997
|
);
|
|
11998
|
+
canAttendeeRequestAiAssistantEnabledSpy = sinon.spy(
|
|
11999
|
+
MeetingUtil,
|
|
12000
|
+
'canAttendeeRequestAiAssistantEnabled'
|
|
12001
|
+
);
|
|
12002
|
+
attendeeRequestAiAssistantDeclinedAllSpy = sinon.spy(
|
|
12003
|
+
MeetingUtil,
|
|
12004
|
+
'attendeeRequestAiAssistantDeclinedAll'
|
|
12005
|
+
);
|
|
11671
12006
|
});
|
|
11672
12007
|
|
|
11673
12008
|
afterEach(() => {
|
|
11674
12009
|
inMeetingActionsSetSpy.restore();
|
|
11675
12010
|
waitingForOthersToJoinSpy.restore();
|
|
11676
12011
|
showAutoEndMeetingWarningSpy.restore();
|
|
12012
|
+
canAttendeeRequestAiAssistantEnabledSpy.restore();
|
|
12013
|
+
attendeeRequestAiAssistantDeclinedAllSpy.restore();
|
|
11677
12014
|
});
|
|
11678
12015
|
|
|
11679
12016
|
forEach(
|
|
@@ -12197,6 +12534,7 @@ describe('plugin-meetings', () => {
|
|
|
12197
12534
|
const userDisplayHints = ['LOCK_CONTROL_UNLOCK'];
|
|
12198
12535
|
meeting.userDisplayHints = ['LOCK_CONTROL_UNLOCK'];
|
|
12199
12536
|
meeting.meetingInfo.supportVoIP = true;
|
|
12537
|
+
meeting.roles = [];
|
|
12200
12538
|
|
|
12201
12539
|
meeting.updateMeetingActions();
|
|
12202
12540
|
|
|
@@ -12212,6 +12550,7 @@ describe('plugin-meetings', () => {
|
|
|
12212
12550
|
assert.calledWith(canUnsetDisallowUnmuteSpy, userDisplayHints);
|
|
12213
12551
|
assert.calledWith(canUserRaiseHandSpy, userDisplayHints);
|
|
12214
12552
|
assert.calledWith(bothLeaveAndEndMeetingAvailableSpy, userDisplayHints);
|
|
12553
|
+
assert.calledWith(requireHostEndMeetingBeforeLeaveSpy, userDisplayHints);
|
|
12215
12554
|
assert.calledWith(canUserLowerAllHandsSpy, userDisplayHints);
|
|
12216
12555
|
assert.calledWith(canUserLowerSomeoneElsesHandSpy, userDisplayHints);
|
|
12217
12556
|
assert.calledWith(waitingForOthersToJoinSpy, userDisplayHints);
|
|
@@ -12223,6 +12562,12 @@ describe('plugin-meetings', () => {
|
|
|
12223
12562
|
assert.calledWith(canMoveToLobbySpy, userDisplayHints);
|
|
12224
12563
|
assert.calledWith(showAutoEndMeetingWarningSpy, userDisplayHints);
|
|
12225
12564
|
assert.calledWith(isSpokenLanguageAutoDetectionEnabledSpy, userDisplayHints);
|
|
12565
|
+
assert.calledWith(
|
|
12566
|
+
canAttendeeRequestAiAssistantEnabledSpy,
|
|
12567
|
+
userDisplayHints,
|
|
12568
|
+
meeting.roles
|
|
12569
|
+
);
|
|
12570
|
+
assert.calledWith(attendeeRequestAiAssistantDeclinedAllSpy, userDisplayHints);
|
|
12226
12571
|
|
|
12227
12572
|
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
12228
12573
|
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
@@ -12365,33 +12710,159 @@ describe('plugin-meetings', () => {
|
|
|
12365
12710
|
|
|
12366
12711
|
describe('#handleDataChannelUrlChange', () => {
|
|
12367
12712
|
let updateLLMConnectionSpy;
|
|
12713
|
+
let updatePSDataChannelSpy;
|
|
12368
12714
|
|
|
12369
12715
|
beforeEach(() => {
|
|
12370
12716
|
updateLLMConnectionSpy = sinon.spy(meeting, 'updateLLMConnection');
|
|
12717
|
+
updatePSDataChannelSpy = sinon.stub(meeting.webinar, 'updatePSDataChannel').resolves();
|
|
12718
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
12371
12719
|
});
|
|
12372
12720
|
|
|
12373
|
-
const check = (
|
|
12374
|
-
|
|
12721
|
+
const check = (
|
|
12722
|
+
url,
|
|
12723
|
+
practiceSessionDatachannelUrl,
|
|
12724
|
+
{expectedMainCalled, expectedPracticeCalled}
|
|
12725
|
+
) => {
|
|
12726
|
+
meeting.handleDataChannelUrlChange(url, practiceSessionDatachannelUrl);
|
|
12375
12727
|
|
|
12376
|
-
if (
|
|
12728
|
+
if (expectedMainCalled) {
|
|
12377
12729
|
assert.calledWith(updateLLMConnectionSpy);
|
|
12378
12730
|
} else {
|
|
12379
12731
|
assert.notCalled(updateLLMConnectionSpy);
|
|
12380
12732
|
}
|
|
12733
|
+
|
|
12734
|
+
if (expectedPracticeCalled) {
|
|
12735
|
+
assert.calledWith(updatePSDataChannelSpy);
|
|
12736
|
+
} else {
|
|
12737
|
+
assert.notCalled(updatePSDataChannelSpy);
|
|
12738
|
+
}
|
|
12381
12739
|
};
|
|
12382
12740
|
|
|
12383
12741
|
it('calls deferred updateLLMConnection if datachannelURL is set and the enableAutomaticLLM is true', () => {
|
|
12384
12742
|
meeting.config.enableAutomaticLLM = true;
|
|
12385
|
-
check('some url', true);
|
|
12743
|
+
check('some url', undefined, {expectedMainCalled: true, expectedPracticeCalled: false});
|
|
12386
12744
|
});
|
|
12387
12745
|
|
|
12388
12746
|
it('does not call updateLLMConnection if datachannelURL is undefined', () => {
|
|
12389
12747
|
meeting.config.enableAutomaticLLM = true;
|
|
12390
|
-
check(undefined,
|
|
12748
|
+
check(undefined, undefined, {
|
|
12749
|
+
expectedMainCalled: false,
|
|
12750
|
+
expectedPracticeCalled: false,
|
|
12751
|
+
});
|
|
12391
12752
|
});
|
|
12392
12753
|
|
|
12393
12754
|
it('does not call updateLLMConnection if enableAutomaticLLM is false', () => {
|
|
12394
|
-
check('some url',
|
|
12755
|
+
check('some url', 'some practice url', {
|
|
12756
|
+
expectedMainCalled: false,
|
|
12757
|
+
expectedPracticeCalled: false,
|
|
12758
|
+
});
|
|
12759
|
+
});
|
|
12760
|
+
|
|
12761
|
+
it('calls updatePSDataChannel when practice-session routing is active', () => {
|
|
12762
|
+
meeting.config.enableAutomaticLLM = true;
|
|
12763
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
12764
|
+
|
|
12765
|
+
check('some url', 'some practice url', {
|
|
12766
|
+
expectedMainCalled: true,
|
|
12767
|
+
expectedPracticeCalled: true,
|
|
12768
|
+
});
|
|
12769
|
+
});
|
|
12770
|
+
|
|
12771
|
+
it('does not call updatePSDataChannel when the main datachannelURL is undefined', () => {
|
|
12772
|
+
meeting.config.enableAutomaticLLM = true;
|
|
12773
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
12774
|
+
|
|
12775
|
+
check(undefined, 'some practice url', {
|
|
12776
|
+
expectedMainCalled: false,
|
|
12777
|
+
expectedPracticeCalled: false,
|
|
12778
|
+
});
|
|
12779
|
+
});
|
|
12780
|
+
});
|
|
12781
|
+
|
|
12782
|
+
describe('#saveDataChannelToken', () => {
|
|
12783
|
+
beforeEach(() => {
|
|
12784
|
+
webex.internal.llm.setDatachannelToken = sinon.stub();
|
|
12785
|
+
});
|
|
12786
|
+
|
|
12787
|
+
it('saves datachannelToken into LLM as Default', () => {
|
|
12788
|
+
meeting.saveDataChannelToken({
|
|
12789
|
+
locus: {
|
|
12790
|
+
self: {datachannelToken: 'default-token'},
|
|
12791
|
+
},
|
|
12792
|
+
});
|
|
12793
|
+
|
|
12794
|
+
assert.calledWithExactly(
|
|
12795
|
+
webex.internal.llm.setDatachannelToken,
|
|
12796
|
+
'default-token',
|
|
12797
|
+
'llm-default-session'
|
|
12798
|
+
);
|
|
12799
|
+
});
|
|
12800
|
+
|
|
12801
|
+
it('saves practiceSessionDatachannelToken into LLM as PracticeSession', () => {
|
|
12802
|
+
meeting.saveDataChannelToken({
|
|
12803
|
+
locus: {
|
|
12804
|
+
self: {practiceSessionDatachannelToken: 'ps-token'},
|
|
12805
|
+
},
|
|
12806
|
+
});
|
|
12807
|
+
|
|
12808
|
+
assert.calledWithExactly(
|
|
12809
|
+
webex.internal.llm.setDatachannelToken,
|
|
12810
|
+
'ps-token',
|
|
12811
|
+
'llm-practice-session'
|
|
12812
|
+
);
|
|
12813
|
+
});
|
|
12814
|
+
|
|
12815
|
+
it('saves both tokens when both are present', () => {
|
|
12816
|
+
meeting.saveDataChannelToken({
|
|
12817
|
+
locus: {
|
|
12818
|
+
self: {
|
|
12819
|
+
datachannelToken: 'default-token',
|
|
12820
|
+
practiceSessionDatachannelToken: 'ps-token',
|
|
12821
|
+
},
|
|
12822
|
+
},
|
|
12823
|
+
});
|
|
12824
|
+
|
|
12825
|
+
assert.calledTwice(webex.internal.llm.setDatachannelToken);
|
|
12826
|
+
assert.calledWithExactly(
|
|
12827
|
+
webex.internal.llm.setDatachannelToken,
|
|
12828
|
+
'default-token',
|
|
12829
|
+
'llm-default-session'
|
|
12830
|
+
);
|
|
12831
|
+
assert.calledWithExactly(
|
|
12832
|
+
webex.internal.llm.setDatachannelToken,
|
|
12833
|
+
'ps-token',
|
|
12834
|
+
'llm-practice-session'
|
|
12835
|
+
);
|
|
12836
|
+
});
|
|
12837
|
+
|
|
12838
|
+
it('does not call setDatachannelToken when no tokens are present', () => {
|
|
12839
|
+
meeting.saveDataChannelToken({locus: {self: {}}});
|
|
12840
|
+
|
|
12841
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12842
|
+
});
|
|
12843
|
+
|
|
12844
|
+
it('handles undefined join gracefully', () => {
|
|
12845
|
+
meeting.saveDataChannelToken(undefined);
|
|
12846
|
+
|
|
12847
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12848
|
+
});
|
|
12849
|
+
|
|
12850
|
+
it('handles missing locus.self gracefully', () => {
|
|
12851
|
+
meeting.saveDataChannelToken({locus: {}});
|
|
12852
|
+
|
|
12853
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12854
|
+
});
|
|
12855
|
+
});
|
|
12856
|
+
|
|
12857
|
+
describe('#clearDataChannelToken', () => {
|
|
12858
|
+
beforeEach(() => {
|
|
12859
|
+
webex.internal.llm.resetDatachannelTokens = sinon.stub();
|
|
12860
|
+
});
|
|
12861
|
+
|
|
12862
|
+
it('calls resetDatachannelTokens on LLM', () => {
|
|
12863
|
+
meeting.clearDataChannelToken();
|
|
12864
|
+
|
|
12865
|
+
assert.calledOnce(webex.internal.llm.resetDatachannelTokens);
|
|
12395
12866
|
});
|
|
12396
12867
|
});
|
|
12397
12868
|
|
|
@@ -12400,16 +12871,20 @@ describe('plugin-meetings', () => {
|
|
|
12400
12871
|
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
12401
12872
|
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
12402
12873
|
webex.internal.llm.getDatachannelUrl = sinon.stub();
|
|
12403
|
-
webex.internal.llm.registerAndConnect = sinon
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
webex.internal.llm.
|
|
12407
|
-
|
|
12408
|
-
|
|
12874
|
+
webex.internal.llm.registerAndConnect = sinon.stub().resolves('something');
|
|
12875
|
+
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
12876
|
+
webex.internal.llm.on = sinon.stub();
|
|
12877
|
+
webex.internal.llm.off = sinon.stub();
|
|
12878
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
|
|
12879
|
+
webex.internal.llm.setDatachannelToken = sinon.stub();
|
|
12880
|
+
|
|
12409
12881
|
meeting.processRelayEvent = sinon.stub();
|
|
12882
|
+
meeting.processLocusLLMEvent = sinon.stub();
|
|
12883
|
+
meeting.clearLLMHealthCheckTimer = sinon.stub();
|
|
12884
|
+
meeting.startLLMHealthCheckTimer = sinon.stub();
|
|
12885
|
+
|
|
12410
12886
|
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
12411
12887
|
});
|
|
12412
|
-
|
|
12413
12888
|
it('does not connect if the call is not joined yet', async () => {
|
|
12414
12889
|
meeting.joinedWith = {state: 'any other state'};
|
|
12415
12890
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
@@ -12423,31 +12898,21 @@ describe('plugin-meetings', () => {
|
|
|
12423
12898
|
assert.equal(result, undefined);
|
|
12424
12899
|
assert.notCalled(meeting.webex.internal.llm.on);
|
|
12425
12900
|
});
|
|
12426
|
-
|
|
12427
12901
|
it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
|
|
12428
12902
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
12434
|
-
|
|
12435
|
-
const result = await meeting.updateLLMConnection();
|
|
12436
|
-
|
|
12437
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12438
|
-
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12439
|
-
assert.equal(result, undefined);
|
|
12440
|
-
assert.notCalled(meeting.webex.internal.llm.on);
|
|
12441
|
-
});
|
|
12442
|
-
|
|
12443
|
-
it('connects if not already connected', async () => {
|
|
12444
|
-
meeting.joinedWith = {state: 'JOINED'};
|
|
12445
|
-
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
12903
|
+
meeting.locusInfo = {
|
|
12904
|
+
url: 'a url',
|
|
12905
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12906
|
+
};
|
|
12446
12907
|
|
|
12447
12908
|
const result = await meeting.updateLLMConnection();
|
|
12448
|
-
|
|
12449
12909
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12450
|
-
assert.
|
|
12910
|
+
assert.calledWithExactly(
|
|
12911
|
+
webex.internal.llm.registerAndConnect,
|
|
12912
|
+
'a url',
|
|
12913
|
+
'a datachannel url',
|
|
12914
|
+
undefined
|
|
12915
|
+
);
|
|
12451
12916
|
assert.equal(result, 'something');
|
|
12452
12917
|
assert.calledWithExactly(
|
|
12453
12918
|
meeting.webex.internal.llm.off,
|
|
@@ -12470,27 +12935,49 @@ describe('plugin-meetings', () => {
|
|
|
12470
12935
|
meeting.processLocusLLMEvent
|
|
12471
12936
|
);
|
|
12472
12937
|
});
|
|
12938
|
+
it('connects if not already connected', async () => {
|
|
12939
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
12940
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
12941
|
+
|
|
12942
|
+
const result = await meeting.updateLLMConnection();
|
|
12473
12943
|
|
|
12474
|
-
|
|
12944
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12945
|
+
assert.calledWithExactly(
|
|
12946
|
+
webex.internal.llm.registerAndConnect,
|
|
12947
|
+
'a url',
|
|
12948
|
+
'a datachannel url',
|
|
12949
|
+
undefined
|
|
12950
|
+
);
|
|
12951
|
+
assert.equal(result, 'something');
|
|
12952
|
+
});
|
|
12953
|
+
it('disconnects if the locus url has changed', async () => {
|
|
12475
12954
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12955
|
+
|
|
12476
12956
|
webex.internal.llm.isConnected.returns(true);
|
|
12477
12957
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12478
|
-
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
12479
12958
|
|
|
12480
|
-
meeting.locusInfo = {
|
|
12959
|
+
meeting.locusInfo = {
|
|
12960
|
+
url: 'a different url',
|
|
12961
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12962
|
+
self: {},
|
|
12963
|
+
};
|
|
12481
12964
|
|
|
12482
12965
|
const result = await meeting.updateLLMConnection();
|
|
12483
12966
|
|
|
12484
|
-
assert.
|
|
12967
|
+
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
12485
12968
|
code: 3050,
|
|
12486
12969
|
reason: 'done (permanent)',
|
|
12487
12970
|
});
|
|
12488
|
-
|
|
12971
|
+
|
|
12972
|
+
assert.calledWithExactly(
|
|
12489
12973
|
webex.internal.llm.registerAndConnect,
|
|
12490
12974
|
'a different url',
|
|
12491
|
-
'a datachannel url'
|
|
12975
|
+
'a datachannel url',
|
|
12976
|
+
undefined
|
|
12492
12977
|
);
|
|
12978
|
+
|
|
12493
12979
|
assert.equal(result, 'something');
|
|
12980
|
+
|
|
12494
12981
|
assert.calledWithExactly(
|
|
12495
12982
|
meeting.webex.internal.llm.off,
|
|
12496
12983
|
'event:relay.event',
|
|
@@ -12502,6 +12989,7 @@ describe('plugin-meetings', () => {
|
|
|
12502
12989
|
meeting.processLocusLLMEvent
|
|
12503
12990
|
);
|
|
12504
12991
|
assert.callCount(meeting.webex.internal.llm.off, 4);
|
|
12992
|
+
|
|
12505
12993
|
assert.calledWithExactly(
|
|
12506
12994
|
meeting.webex.internal.llm.on,
|
|
12507
12995
|
'event:relay.event',
|
|
@@ -12512,28 +13000,37 @@ describe('plugin-meetings', () => {
|
|
|
12512
13000
|
'event:locus.state_message',
|
|
12513
13001
|
meeting.processLocusLLMEvent
|
|
12514
13002
|
);
|
|
13003
|
+
assert.isFalse(
|
|
13004
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13005
|
+
);
|
|
12515
13006
|
});
|
|
12516
|
-
|
|
12517
|
-
it('disconnects it first if the data channel url has changed', async () => {
|
|
13007
|
+
it('disconnects if the data channel url has changed', async () => {
|
|
12518
13008
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12519
13009
|
webex.internal.llm.isConnected.returns(true);
|
|
12520
13010
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12521
|
-
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
12522
13011
|
|
|
12523
|
-
meeting.locusInfo = {
|
|
13012
|
+
meeting.locusInfo = {
|
|
13013
|
+
url: 'a url',
|
|
13014
|
+
info: {datachannelUrl: 'a different datachannel url'},
|
|
13015
|
+
self: {},
|
|
13016
|
+
};
|
|
12524
13017
|
|
|
12525
13018
|
const result = await meeting.updateLLMConnection();
|
|
12526
13019
|
|
|
12527
|
-
assert.
|
|
13020
|
+
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
12528
13021
|
code: 3050,
|
|
12529
13022
|
reason: 'done (permanent)',
|
|
12530
13023
|
});
|
|
12531
|
-
|
|
13024
|
+
|
|
13025
|
+
assert.calledWithExactly(
|
|
12532
13026
|
webex.internal.llm.registerAndConnect,
|
|
12533
13027
|
'a url',
|
|
12534
|
-
'a different datachannel url'
|
|
13028
|
+
'a different datachannel url',
|
|
13029
|
+
undefined
|
|
12535
13030
|
);
|
|
13031
|
+
|
|
12536
13032
|
assert.equal(result, 'something');
|
|
13033
|
+
|
|
12537
13034
|
assert.calledWithExactly(
|
|
12538
13035
|
meeting.webex.internal.llm.off,
|
|
12539
13036
|
'event:relay.event',
|
|
@@ -12544,6 +13041,7 @@ describe('plugin-meetings', () => {
|
|
|
12544
13041
|
'event:locus.state_message',
|
|
12545
13042
|
meeting.processLocusLLMEvent
|
|
12546
13043
|
);
|
|
13044
|
+
|
|
12547
13045
|
assert.calledWithExactly(
|
|
12548
13046
|
meeting.webex.internal.llm.on,
|
|
12549
13047
|
'event:relay.event',
|
|
@@ -12554,8 +13052,10 @@ describe('plugin-meetings', () => {
|
|
|
12554
13052
|
'event:locus.state_message',
|
|
12555
13053
|
meeting.processLocusLLMEvent
|
|
12556
13054
|
);
|
|
13055
|
+
assert.isFalse(
|
|
13056
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13057
|
+
);
|
|
12557
13058
|
});
|
|
12558
|
-
|
|
12559
13059
|
it('disconnects when the state is not JOINED', async () => {
|
|
12560
13060
|
meeting.joinedWith = {state: 'any other state'};
|
|
12561
13061
|
webex.internal.llm.isConnected.returns(true);
|
|
@@ -12565,9 +13065,38 @@ describe('plugin-meetings', () => {
|
|
|
12565
13065
|
|
|
12566
13066
|
const result = await meeting.updateLLMConnection();
|
|
12567
13067
|
|
|
12568
|
-
assert.calledWith(webex.internal.llm.disconnectLLM,
|
|
13068
|
+
assert.calledWith(webex.internal.llm.disconnectLLM, {
|
|
13069
|
+
code: 3050,
|
|
13070
|
+
reason: 'done (permanent)',
|
|
13071
|
+
});
|
|
12569
13072
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12570
13073
|
assert.equal(result, undefined);
|
|
13074
|
+
assert.isFalse(
|
|
13075
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13076
|
+
);
|
|
13077
|
+
});
|
|
13078
|
+
it('rethrows disconnect errors during reconnect cleanup after removing relay listeners and timer', async () => {
|
|
13079
|
+
const disconnectError = new Error('disconnect failed');
|
|
13080
|
+
|
|
13081
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13082
|
+
webex.internal.llm.isConnected.returns(true);
|
|
13083
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
13084
|
+
webex.internal.llm.disconnectLLM.rejects(disconnectError);
|
|
13085
|
+
|
|
13086
|
+
meeting.locusInfo = {
|
|
13087
|
+
url: 'a different url',
|
|
13088
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13089
|
+
self: {},
|
|
13090
|
+
};
|
|
13091
|
+
|
|
13092
|
+
try {
|
|
13093
|
+
await meeting.updateLLMConnection();
|
|
13094
|
+
assert.fail('Expected updateLLMConnection to reject when disconnectLLM fails');
|
|
13095
|
+
} catch (error) {
|
|
13096
|
+
assert.equal(error, disconnectError);
|
|
13097
|
+
}
|
|
13098
|
+
|
|
13099
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12571
13100
|
assert.calledWithExactly(
|
|
12572
13101
|
meeting.webex.internal.llm.off,
|
|
12573
13102
|
'event:relay.event',
|
|
@@ -12578,22 +13107,159 @@ describe('plugin-meetings', () => {
|
|
|
12578
13107
|
'event:locus.state_message',
|
|
12579
13108
|
meeting.processLocusLLMEvent
|
|
12580
13109
|
);
|
|
13110
|
+
assert.isFalse(
|
|
13111
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13112
|
+
);
|
|
13113
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
12581
13114
|
});
|
|
12582
|
-
|
|
12583
|
-
it('connect ps data channel if ps started in webinar', async () => {
|
|
13115
|
+
it('still need connect main session data channel when PS started', async () => {
|
|
12584
13116
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12585
13117
|
meeting.locusInfo = {
|
|
12586
13118
|
url: 'a url',
|
|
12587
13119
|
info: {
|
|
12588
13120
|
datachannelUrl: 'a datachannel url',
|
|
12589
|
-
practiceSessionDatachannelUrl: '
|
|
13121
|
+
practiceSessionDatachannelUrl: 'ps-url',
|
|
12590
13122
|
},
|
|
12591
13123
|
};
|
|
12592
|
-
meeting.webinar.isJoinPracticeSessionDataChannel
|
|
13124
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
13125
|
+
|
|
12593
13126
|
await meeting.updateLLMConnection();
|
|
12594
13127
|
|
|
12595
|
-
assert.
|
|
12596
|
-
|
|
13128
|
+
assert.calledWithExactly(
|
|
13129
|
+
webex.internal.llm.registerAndConnect,
|
|
13130
|
+
'a url',
|
|
13131
|
+
'a datachannel url',
|
|
13132
|
+
undefined
|
|
13133
|
+
);
|
|
13134
|
+
});
|
|
13135
|
+
it('passes dataChannelToken from LLM to registerAndConnect', async () => {
|
|
13136
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13137
|
+
meeting.locusInfo = {
|
|
13138
|
+
url: 'a url',
|
|
13139
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13140
|
+
};
|
|
13141
|
+
|
|
13142
|
+
webex.internal.llm.getDatachannelToken.withArgs('llm-default-session').returns('token-123');
|
|
13143
|
+
|
|
13144
|
+
await meeting.updateLLMConnection();
|
|
13145
|
+
|
|
13146
|
+
assert.calledWithExactly(
|
|
13147
|
+
webex.internal.llm.registerAndConnect,
|
|
13148
|
+
'a url',
|
|
13149
|
+
'a datachannel url',
|
|
13150
|
+
'token-123'
|
|
13151
|
+
);
|
|
13152
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13153
|
+
});
|
|
13154
|
+
it('passes undefined token when LLM has no token stored', async () => {
|
|
13155
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13156
|
+
meeting.locusInfo = {
|
|
13157
|
+
url: 'a url',
|
|
13158
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13159
|
+
};
|
|
13160
|
+
|
|
13161
|
+
webex.internal.llm.getDatachannelToken.returns(undefined);
|
|
13162
|
+
|
|
13163
|
+
await meeting.updateLLMConnection();
|
|
13164
|
+
|
|
13165
|
+
assert.calledWithExactly(
|
|
13166
|
+
webex.internal.llm.registerAndConnect,
|
|
13167
|
+
'a url',
|
|
13168
|
+
'a datachannel url',
|
|
13169
|
+
undefined
|
|
13170
|
+
);
|
|
13171
|
+
|
|
13172
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13173
|
+
});
|
|
13174
|
+
|
|
13175
|
+
it('does not pass token when data channel with jwt token is disabled', async () => {
|
|
13176
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13177
|
+
meeting.locusInfo = {
|
|
13178
|
+
url: 'a url',
|
|
13179
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13180
|
+
};
|
|
13181
|
+
|
|
13182
|
+
webex.internal.llm.getDatachannelToken.returns(undefined);
|
|
13183
|
+
webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
|
|
13184
|
+
|
|
13185
|
+
await meeting.updateLLMConnection();
|
|
13186
|
+
|
|
13187
|
+
assert.calledWithExactly(
|
|
13188
|
+
webex.internal.llm.registerAndConnect,
|
|
13189
|
+
'a url',
|
|
13190
|
+
'a datachannel url',
|
|
13191
|
+
undefined
|
|
13192
|
+
);
|
|
13193
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13194
|
+
});
|
|
13195
|
+
|
|
13196
|
+
describe('#clearMeetingData', () => {
|
|
13197
|
+
beforeEach(() => {
|
|
13198
|
+
webex.internal.llm.isConnected = sinon.stub().returns(true);
|
|
13199
|
+
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
13200
|
+
webex.internal.llm.off = sinon.stub();
|
|
13201
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
13202
|
+
meeting.clearLLMHealthCheckTimer = sinon.stub();
|
|
13203
|
+
meeting.stopTranscription = sinon.stub();
|
|
13204
|
+
meeting.clearDataChannelToken = sinon.stub();
|
|
13205
|
+
meeting.shareStatus = 'no-share';
|
|
13206
|
+
});
|
|
13207
|
+
|
|
13208
|
+
it('disconnects llm and removes online and relay listeners during meeting data cleanup', async () => {
|
|
13209
|
+
await meeting.clearMeetingData();
|
|
13210
|
+
|
|
13211
|
+
assert.calledOnceWithExactly(webex.internal.llm.disconnectLLM, {
|
|
13212
|
+
code: 3050,
|
|
13213
|
+
reason: 'done (permanent)',
|
|
13214
|
+
});
|
|
13215
|
+
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
13216
|
+
assert.calledWithExactly(
|
|
13217
|
+
webex.internal.llm.off,
|
|
13218
|
+
'event:relay.event',
|
|
13219
|
+
meeting.processRelayEvent
|
|
13220
|
+
);
|
|
13221
|
+
assert.calledWithExactly(
|
|
13222
|
+
webex.internal.llm.off,
|
|
13223
|
+
'event:locus.state_message',
|
|
13224
|
+
meeting.processLocusLLMEvent
|
|
13225
|
+
);
|
|
13226
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13227
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13228
|
+
assert.isUndefined(meeting.transcription);
|
|
13229
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13230
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13231
|
+
});
|
|
13232
|
+
it('continues cleanup when disconnectLLM fails during meeting data cleanup', async () => {
|
|
13233
|
+
webex.internal.llm.disconnectLLM.rejects(new Error('disconnect failed'));
|
|
13234
|
+
|
|
13235
|
+
await meeting.clearMeetingData();
|
|
13236
|
+
|
|
13237
|
+
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
13238
|
+
assert.calledWithExactly(
|
|
13239
|
+
webex.internal.llm.off,
|
|
13240
|
+
'event:relay.event',
|
|
13241
|
+
meeting.processRelayEvent
|
|
13242
|
+
);
|
|
13243
|
+
assert.calledWithExactly(
|
|
13244
|
+
webex.internal.llm.off,
|
|
13245
|
+
'event:locus.state_message',
|
|
13246
|
+
meeting.processLocusLLMEvent
|
|
13247
|
+
);
|
|
13248
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13249
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13250
|
+
assert.isUndefined(meeting.transcription);
|
|
13251
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13252
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13253
|
+
});
|
|
13254
|
+
it('always calls stopTranscription even when transcription is undefined', async () => {
|
|
13255
|
+
meeting.transcription = undefined;
|
|
13256
|
+
|
|
13257
|
+
await meeting.clearMeetingData();
|
|
13258
|
+
|
|
13259
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13260
|
+
assert.isUndefined(meeting.transcription);
|
|
13261
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13262
|
+
});
|
|
12597
13263
|
});
|
|
12598
13264
|
});
|
|
12599
13265
|
|
|
@@ -12604,6 +13270,7 @@ describe('plugin-meetings', () => {
|
|
|
12604
13270
|
|
|
12605
13271
|
it('should read the locus object, set on the meeting and return null', () => {
|
|
12606
13272
|
const dataSets = {someFakeStuff: 'dataSet'};
|
|
13273
|
+
const metadata = {some: 'metadata'};
|
|
12607
13274
|
|
|
12608
13275
|
meeting.setLocus({
|
|
12609
13276
|
mediaConnections: [test1],
|
|
@@ -12613,12 +13280,14 @@ describe('plugin-meetings', () => {
|
|
|
12613
13280
|
mediaId: uuid3,
|
|
12614
13281
|
locus: {host: {id: uuid4}},
|
|
12615
13282
|
dataSets,
|
|
13283
|
+
metadata,
|
|
12616
13284
|
});
|
|
12617
13285
|
assert.calledOnce(meeting.locusInfo.initialSetup);
|
|
12618
13286
|
assert.calledWith(meeting.locusInfo.initialSetup, {
|
|
12619
13287
|
trigger: 'join-response',
|
|
12620
13288
|
locus: {host: {id: uuid4}},
|
|
12621
13289
|
dataSets,
|
|
13290
|
+
metadata,
|
|
12622
13291
|
});
|
|
12623
13292
|
assert.equal(meeting.mediaConnections, test1);
|
|
12624
13293
|
assert.equal(meeting.locusUrl, url1);
|
|
@@ -14160,6 +14829,69 @@ describe('plugin-meetings', () => {
|
|
|
14160
14829
|
assert.calledOnce(meeting.meetingRequest.keepAlive);
|
|
14161
14830
|
});
|
|
14162
14831
|
});
|
|
14832
|
+
describe('#refreshDataChannelToken()', () => {
|
|
14833
|
+
let meeting;
|
|
14834
|
+
|
|
14835
|
+
beforeEach(() => {
|
|
14836
|
+
meeting = Object.create(Meeting.prototype);
|
|
14837
|
+
meeting.locusUrl = 'https://locus.example.com';
|
|
14838
|
+
meeting.meetingRequest = {
|
|
14839
|
+
fetchDatachannelToken: sinon.stub().resolves({
|
|
14840
|
+
body: {datachannelToken: 'mock-token'},
|
|
14841
|
+
}),
|
|
14842
|
+
};
|
|
14843
|
+
meeting.members = {
|
|
14844
|
+
selfId: 'self-123',
|
|
14845
|
+
};
|
|
14846
|
+
meeting.webinar = {
|
|
14847
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
|
|
14848
|
+
};
|
|
14849
|
+
});
|
|
14850
|
+
|
|
14851
|
+
it('calls fetchDatachannelToken with correct parameters', async () => {
|
|
14852
|
+
await meeting.refreshDataChannelToken();
|
|
14853
|
+
|
|
14854
|
+
sinon.assert.calledOnce(meeting.meetingRequest.fetchDatachannelToken);
|
|
14855
|
+
|
|
14856
|
+
sinon.assert.calledWith(meeting.meetingRequest.fetchDatachannelToken, {
|
|
14857
|
+
locusUrl: 'https://locus.example.com',
|
|
14858
|
+
requestingParticipantId: 'self-123',
|
|
14859
|
+
isPracticeSession: true,
|
|
14860
|
+
});
|
|
14861
|
+
});
|
|
14862
|
+
|
|
14863
|
+
it('returns the correct structured result', async () => {
|
|
14864
|
+
const result = await meeting.refreshDataChannelToken();
|
|
14865
|
+
|
|
14866
|
+
expect(result).to.deep.equal({
|
|
14867
|
+
body: {
|
|
14868
|
+
datachannelToken: 'mock-token',
|
|
14869
|
+
dataChannelTokenType: 'llm-practice-session',
|
|
14870
|
+
},
|
|
14871
|
+
});
|
|
14872
|
+
});
|
|
14873
|
+
});
|
|
14874
|
+
describe('#getDataChannelTokenType', () => {
|
|
14875
|
+
it('returns PracticeSession when webinar is in practice session mode', () => {
|
|
14876
|
+
meeting.webinar = {
|
|
14877
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
|
|
14878
|
+
};
|
|
14879
|
+
|
|
14880
|
+
const result = meeting.getDataChannelTokenType();
|
|
14881
|
+
|
|
14882
|
+
expect(result).to.equal('llm-practice-session');
|
|
14883
|
+
});
|
|
14884
|
+
|
|
14885
|
+
it('returns Default when not in practice session mode', () => {
|
|
14886
|
+
meeting.webinar = {
|
|
14887
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(false),
|
|
14888
|
+
};
|
|
14889
|
+
|
|
14890
|
+
const result = meeting.getDataChannelTokenType();
|
|
14891
|
+
|
|
14892
|
+
expect(result).to.equal('llm-default-session');
|
|
14893
|
+
});
|
|
14894
|
+
});
|
|
14163
14895
|
describe('#stopKeepAlive', () => {
|
|
14164
14896
|
let clock;
|
|
14165
14897
|
const defaultKeepAliveUrl = 'keep.alive.url';
|