@webex/plugin-meetings 3.11.0 → 3.12.0-next.2
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 +3 -1
- 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 +850 -410
- 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 +10 -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 +1173 -877
- 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 +2 -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/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 +1 -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 +6 -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 +61 -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 +1 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/types/webinar/utils.d.ts +6 -0
- package/dist/webinar/index.js +291 -91
- 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 +1 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +745 -252
- package/src/hashTree/types.ts +4 -0
- package/src/hashTree/utils.ts +9 -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 +291 -76
- 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 +1 -0
- package/src/multistream/mediaRequestManager.ts +4 -54
- package/src/multistream/remoteMediaManager.ts +13 -0
- 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 +191 -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 +2225 -189
- 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 +829 -115
- 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/reachability/index.ts +23 -0
- package/test/unit/spec/reconnection-manager/index.js +4 -8
- package/test/unit/spec/webinar/index.ts +474 -37
- package/test/unit/spec/webinar/utils.ts +39 -0
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
MediaType,
|
|
47
47
|
} from '@webex/internal-media-core';
|
|
48
48
|
import {LocalStreamEventNames} from '@webex/media-helpers';
|
|
49
|
+
import {CapabilityState, WebCapabilities} from '@webex/web-capabilities';
|
|
49
50
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
50
51
|
import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
|
|
51
52
|
import Meeting from '@webex/plugin-meetings/src/meeting';
|
|
@@ -81,6 +82,7 @@ import Mercury from '@webex/internal-plugin-mercury';
|
|
|
81
82
|
import Breakouts from '@webex/plugin-meetings/src/breakouts';
|
|
82
83
|
import SimultaneousInterpretation from '@webex/plugin-meetings/src/interpretation';
|
|
83
84
|
import Webinar from '@webex/plugin-meetings/src/webinar';
|
|
85
|
+
import AIEnableRequest from '@webex/plugin-meetings/src/aiEnableRequest';
|
|
84
86
|
import {REACTION_RELAY_TYPES} from '../../../../src/reactions/constants';
|
|
85
87
|
import locus from '../fixture/locus';
|
|
86
88
|
import {
|
|
@@ -122,7 +124,6 @@ import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
|
|
|
122
124
|
import {createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
|
|
123
125
|
import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
|
|
124
126
|
import {EventEmitter} from 'stream';
|
|
125
|
-
|
|
126
127
|
describe('plugin-meetings', () => {
|
|
127
128
|
const logger = {
|
|
128
129
|
info: () => {},
|
|
@@ -264,7 +265,9 @@ describe('plugin-meetings', () => {
|
|
|
264
265
|
stopReachability: sinon.stub(),
|
|
265
266
|
isSubnetReachable: sinon.stub().returns(true),
|
|
266
267
|
};
|
|
268
|
+
webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
|
|
267
269
|
webex.internal.llm.on = sinon.stub();
|
|
270
|
+
webex.internal.voicea.announce = sinon.stub();
|
|
268
271
|
webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
|
|
269
272
|
{},
|
|
270
273
|
{parent: webex}
|
|
@@ -375,6 +378,7 @@ describe('plugin-meetings', () => {
|
|
|
375
378
|
assert.instanceOf(meeting.breakouts, Breakouts);
|
|
376
379
|
assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
|
|
377
380
|
assert.instanceOf(meeting.webinar, Webinar);
|
|
381
|
+
assert.instanceOf(meeting.aiEnableRequest, AIEnableRequest);
|
|
378
382
|
});
|
|
379
383
|
|
|
380
384
|
it('should call the callback with the meeting that has id already set', () => {
|
|
@@ -734,8 +738,13 @@ describe('plugin-meetings', () => {
|
|
|
734
738
|
let handleTurnDiscoveryHttpResponseStub;
|
|
735
739
|
let abortTurnDiscoveryStub;
|
|
736
740
|
let addMediaInternalStub;
|
|
741
|
+
let supportsRTCPeerConnectionStub;
|
|
737
742
|
|
|
738
743
|
beforeEach(() => {
|
|
744
|
+
supportsRTCPeerConnectionStub = sinon
|
|
745
|
+
.stub(WebCapabilities, 'supportsRTCPeerConnection')
|
|
746
|
+
.returns(CapabilityState.CAPABLE);
|
|
747
|
+
|
|
739
748
|
meeting.join = sinon.stub().callsFake((joinOptions) => {
|
|
740
749
|
meeting.isMultistream = joinOptions.enableMultistream;
|
|
741
750
|
return Promise.resolve(fakeJoinResult);
|
|
@@ -1007,33 +1016,53 @@ describe('plugin-meetings', () => {
|
|
|
1007
1016
|
);
|
|
1008
1017
|
});
|
|
1009
1018
|
|
|
1010
|
-
it('should call leave() if addMediaInternal() fails ', async () => {
|
|
1019
|
+
it('should call leave() if addMediaInternal() fails with a browser media error (TypeError)', async () => {
|
|
1011
1020
|
const addMediaError = new Error('fake addMedia error');
|
|
1012
|
-
addMediaError.name = 'TypeError';
|
|
1021
|
+
addMediaError.name = 'TypeError'; // This makes it a browser media error
|
|
1013
1022
|
|
|
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();
|
|
1023
|
+
const leaveStub = sinon.stub(meeting, 'leave').resolves();
|
|
1024
|
+
meeting.addMediaInternal = sinon.stub().rejects(addMediaError);
|
|
1025
1025
|
|
|
1026
|
-
|
|
1026
|
+
// When a browser media error occurs, it gets transformed into a special structure
|
|
1027
|
+
const rejectedError = await assert.isRejected(
|
|
1027
1028
|
meeting.joinWithMedia({
|
|
1028
1029
|
joinOptions,
|
|
1029
1030
|
mediaOptions,
|
|
1030
|
-
})
|
|
1031
|
-
rejectError
|
|
1031
|
+
})
|
|
1032
1032
|
);
|
|
1033
1033
|
|
|
1034
|
+
// Verify the error was transformed with errorCode 2729
|
|
1035
|
+
assert.equal(rejectedError.error.body.errorCode, 2729);
|
|
1036
|
+
assert.equal(rejectedError.error.body.message, 'fake addMedia error');
|
|
1037
|
+
assert.equal(rejectedError.error.body.name, 'TypeError');
|
|
1038
|
+
|
|
1034
1039
|
assert.calledOnce(meeting.join);
|
|
1035
1040
|
assert.calledOnce(meeting.addMediaInternal);
|
|
1041
|
+
assert.calledOnce(leaveStub);
|
|
1042
|
+
assert.calledOnceWithExactly(leaveStub, {
|
|
1043
|
+
resourceId: undefined,
|
|
1044
|
+
reason: 'joinWithMedia failure',
|
|
1045
|
+
});
|
|
1046
|
+
|
|
1047
|
+
// Browser media errors don't retry, so behavioral metric is sent only once
|
|
1048
|
+
// NOTE: The error gets transformed, so the metric receives undefined for message/stack/name
|
|
1049
|
+
// because they're now nested in error.body instead of at the top level
|
|
1036
1050
|
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
1051
|
+
assert.calledWith(
|
|
1052
|
+
Metrics.sendBehavioralMetric,
|
|
1053
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
1054
|
+
{
|
|
1055
|
+
correlation_id: meeting.correlationId,
|
|
1056
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1057
|
+
reason: undefined, // transformed error doesn't have .message at top level
|
|
1058
|
+
stack: undefined, // transformed error doesn't have .stack at top level
|
|
1059
|
+
leaveErrorReason: undefined,
|
|
1060
|
+
isRetry: false,
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
type: undefined, // transformed error doesn't have .name at top level
|
|
1064
|
+
}
|
|
1065
|
+
);
|
|
1037
1066
|
});
|
|
1038
1067
|
|
|
1039
1068
|
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 +1273,54 @@ describe('plugin-meetings', () => {
|
|
|
1244
1273
|
await assert.isRejected(result);
|
|
1245
1274
|
});
|
|
1246
1275
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1276
|
+
[
|
|
1277
|
+
{
|
|
1278
|
+
errorName: 'SdpOfferCreationError',
|
|
1279
|
+
description: 'if we fail to create the offer on first attempt',
|
|
1280
|
+
},
|
|
1281
|
+
{
|
|
1282
|
+
errorName: 'WebrtcApiNotAvailableError',
|
|
1283
|
+
description: 'if RTCPeerConnection is not available',
|
|
1284
|
+
},
|
|
1285
|
+
].forEach(({errorName, description}) => {
|
|
1286
|
+
it(`should not attempt a retry ${description}`, async () => {
|
|
1287
|
+
const addMediaError = new Error('fake addMedia error');
|
|
1288
|
+
addMediaError.name = errorName;
|
|
1250
1289
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1290
|
+
meeting.addMediaInternal.rejects(addMediaError);
|
|
1291
|
+
sinon.stub(meeting, 'leave').resolves();
|
|
1253
1292
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1293
|
+
await assert.isRejected(
|
|
1294
|
+
meeting.joinWithMedia({
|
|
1295
|
+
joinOptions,
|
|
1296
|
+
mediaOptions,
|
|
1297
|
+
}),
|
|
1298
|
+
addMediaError
|
|
1299
|
+
);
|
|
1261
1300
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1301
|
+
// check that only 1 attempt was done
|
|
1302
|
+
assert.calledOnce(meeting.join);
|
|
1303
|
+
assert.calledOnce(meeting.addMediaInternal);
|
|
1304
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
1305
|
+
assert.calledWith(
|
|
1306
|
+
Metrics.sendBehavioralMetric.firstCall,
|
|
1307
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
1308
|
+
{
|
|
1309
|
+
correlation_id: meeting.correlationId,
|
|
1310
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1311
|
+
reason: addMediaError.message,
|
|
1312
|
+
stack: addMediaError.stack,
|
|
1313
|
+
leaveErrorReason: undefined,
|
|
1314
|
+
isRetry: false,
|
|
1315
|
+
},
|
|
1316
|
+
{
|
|
1317
|
+
type: addMediaError.name,
|
|
1318
|
+
}
|
|
1319
|
+
);
|
|
1320
|
+
assert.calledOnceWithExactly(meeting.leave, {
|
|
1321
|
+
resourceId: undefined,
|
|
1322
|
+
reason: 'joinWithMedia failure',
|
|
1323
|
+
});
|
|
1284
1324
|
});
|
|
1285
1325
|
});
|
|
1286
1326
|
|
|
@@ -1349,6 +1389,21 @@ describe('plugin-meetings', () => {
|
|
|
1349
1389
|
})
|
|
1350
1390
|
);
|
|
1351
1391
|
});
|
|
1392
|
+
|
|
1393
|
+
it('should throw immediately if RTCPeerConnection is not available', async () => {
|
|
1394
|
+
supportsRTCPeerConnectionStub.returns(CapabilityState.NOT_CAPABLE);
|
|
1395
|
+
|
|
1396
|
+
await assert.isRejected(
|
|
1397
|
+
meeting.joinWithMedia({
|
|
1398
|
+
joinOptions,
|
|
1399
|
+
mediaOptions,
|
|
1400
|
+
}),
|
|
1401
|
+
Errors.WebrtcApiNotAvailableError
|
|
1402
|
+
);
|
|
1403
|
+
|
|
1404
|
+
assert.notCalled(meeting.join);
|
|
1405
|
+
assert.notCalled(meeting.addMediaInternal);
|
|
1406
|
+
});
|
|
1352
1407
|
});
|
|
1353
1408
|
describe('#isTranscriptionSupported', () => {
|
|
1354
1409
|
it('should return false if the feature is not supported for the meeting', () => {
|
|
@@ -1497,6 +1552,22 @@ describe('plugin-meetings', () => {
|
|
|
1497
1552
|
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
1498
1553
|
);
|
|
1499
1554
|
});
|
|
1555
|
+
|
|
1556
|
+
it('should stop listening to voicea events even when transcription is undefined', () => {
|
|
1557
|
+
meeting.transcription = undefined;
|
|
1558
|
+
meeting.stopTranscription();
|
|
1559
|
+
assert.equal(webex.internal.voicea.off.callCount, 4);
|
|
1560
|
+
assert.equal(meeting.areVoiceaEventsSetup, false);
|
|
1561
|
+
assert.calledWith(
|
|
1562
|
+
TriggerProxy.trigger,
|
|
1563
|
+
sinon.match.instanceOf(Meeting),
|
|
1564
|
+
{
|
|
1565
|
+
file: 'meeting/index',
|
|
1566
|
+
function: 'triggerStopReceivingTranscriptionEvent',
|
|
1567
|
+
},
|
|
1568
|
+
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
1569
|
+
);
|
|
1570
|
+
});
|
|
1500
1571
|
});
|
|
1501
1572
|
|
|
1502
1573
|
describe('#setCaptionLanguage', () => {
|
|
@@ -1858,6 +1929,53 @@ describe('plugin-meetings', () => {
|
|
|
1858
1929
|
fakeProcessedReaction
|
|
1859
1930
|
);
|
|
1860
1931
|
});
|
|
1932
|
+
|
|
1933
|
+
it('should process if participantId does not exist in membersCollection but has displayName in Webinar', () => {
|
|
1934
|
+
LoggerProxy.logger.warn = sinon.stub();
|
|
1935
|
+
meeting.isReactionsSupported = sinon.stub().returns(true);
|
|
1936
|
+
meeting.config.receiveReactions = true;
|
|
1937
|
+
meeting.locusInfo.info = {isWebinar: true};
|
|
1938
|
+
const fakeSendersName = 'Fake reactors name';
|
|
1939
|
+
const fakeReactionPayload = {
|
|
1940
|
+
type: 'fake_type',
|
|
1941
|
+
codepoints: 'fake_codepoints',
|
|
1942
|
+
shortcodes: 'fake_shortcodes',
|
|
1943
|
+
tone: {
|
|
1944
|
+
type: 'fake_tone_type',
|
|
1945
|
+
codepoints: 'fake_tone_codepoints',
|
|
1946
|
+
shortcodes: 'fake_tone_shortcodes',
|
|
1947
|
+
},
|
|
1948
|
+
};
|
|
1949
|
+
const fakeSenderPayload = {
|
|
1950
|
+
displayName: 'Fake reactors name',
|
|
1951
|
+
participantId: 'fake_participant_id',
|
|
1952
|
+
};
|
|
1953
|
+
const fakeProcessedReaction = {
|
|
1954
|
+
reaction: fakeReactionPayload,
|
|
1955
|
+
sender: {
|
|
1956
|
+
id: fakeSenderPayload.participantId,
|
|
1957
|
+
name: fakeSendersName,
|
|
1958
|
+
},
|
|
1959
|
+
};
|
|
1960
|
+
const fakeRelayEvent = {
|
|
1961
|
+
data: {
|
|
1962
|
+
relayType: REACTION_RELAY_TYPES.REACTION,
|
|
1963
|
+
reaction: fakeReactionPayload,
|
|
1964
|
+
sender: fakeSenderPayload,
|
|
1965
|
+
},
|
|
1966
|
+
};
|
|
1967
|
+
meeting.processRelayEvent(fakeRelayEvent);
|
|
1968
|
+
assert.calledWith(
|
|
1969
|
+
TriggerProxy.trigger,
|
|
1970
|
+
sinon.match.instanceOf(Meeting),
|
|
1971
|
+
{
|
|
1972
|
+
file: 'meeting/index',
|
|
1973
|
+
function: 'join',
|
|
1974
|
+
},
|
|
1975
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
1976
|
+
fakeProcessedReaction
|
|
1977
|
+
);
|
|
1978
|
+
});
|
|
1861
1979
|
});
|
|
1862
1980
|
|
|
1863
1981
|
describe('#handleLLMOnline', () => {
|
|
@@ -3004,6 +3122,111 @@ describe('plugin-meetings', () => {
|
|
|
3004
3122
|
checkWorking({allowMediaInLobby: true});
|
|
3005
3123
|
});
|
|
3006
3124
|
|
|
3125
|
+
const setupLobbyTest = () => {
|
|
3126
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
3127
|
+
.stub()
|
|
3128
|
+
.resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
|
3129
|
+
|
|
3130
|
+
meeting.meetingState = 'ACTIVE';
|
|
3131
|
+
meeting.locusInfo.parsedLocus = {self: {state: 'IDLE'}};
|
|
3132
|
+
meeting.isUserUnadmitted = true;
|
|
3133
|
+
|
|
3134
|
+
// Mock locusMediaRequest
|
|
3135
|
+
meeting.locusMediaRequest = {
|
|
3136
|
+
send: sinon.stub().resolves(),
|
|
3137
|
+
isConfluenceCreated: sinon.stub().returns(false),
|
|
3138
|
+
};
|
|
3139
|
+
|
|
3140
|
+
sinon.stub(RemoteMediaManagerModule, 'RemoteMediaManager').returns({
|
|
3141
|
+
start: sinon.stub().resolves(),
|
|
3142
|
+
on: sinon.stub(),
|
|
3143
|
+
logAllReceiveSlots: sinon.stub(),
|
|
3144
|
+
});
|
|
3145
|
+
|
|
3146
|
+
meeting.isMultistream = true;
|
|
3147
|
+
|
|
3148
|
+
const createFakeStream = (id) => ({
|
|
3149
|
+
on: sinon.stub(),
|
|
3150
|
+
off: sinon.stub(),
|
|
3151
|
+
userMuted: false,
|
|
3152
|
+
systemMuted: false,
|
|
3153
|
+
get muted() {
|
|
3154
|
+
return this.userMuted || this.systemMuted;
|
|
3155
|
+
},
|
|
3156
|
+
setUnmuteAllowed: sinon.stub(),
|
|
3157
|
+
setUserMuted: sinon.stub(),
|
|
3158
|
+
outputStream: {
|
|
3159
|
+
getTracks: () => [{id}],
|
|
3160
|
+
},
|
|
3161
|
+
getSettings: sinon.stub().returns({}),
|
|
3162
|
+
});
|
|
3163
|
+
|
|
3164
|
+
return {
|
|
3165
|
+
fakeMicrophoneStream: createFakeStream('fake mic'),
|
|
3166
|
+
fakeCameraStream: createFakeStream('fake camera'),
|
|
3167
|
+
};
|
|
3168
|
+
};
|
|
3169
|
+
|
|
3170
|
+
it('should not publish any local streams when in the lobby and allowPublishMediaInLobby is false', async () => {
|
|
3171
|
+
const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
|
|
3172
|
+
|
|
3173
|
+
const publishStreamStub = sinon.stub();
|
|
3174
|
+
fakeMediaConnection.createSendSlot = sinon.stub().returns({
|
|
3175
|
+
publishStream: publishStreamStub,
|
|
3176
|
+
unpublishStream: sinon.stub(),
|
|
3177
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3178
|
+
});
|
|
3179
|
+
|
|
3180
|
+
await meeting.addMedia({
|
|
3181
|
+
allowMediaInLobby: true,
|
|
3182
|
+
allowPublishMediaInLobby: false,
|
|
3183
|
+
audioEnabled: true,
|
|
3184
|
+
videoEnabled: true,
|
|
3185
|
+
localStreams: {
|
|
3186
|
+
microphone: fakeMicrophoneStream,
|
|
3187
|
+
camera: fakeCameraStream,
|
|
3188
|
+
},
|
|
3189
|
+
});
|
|
3190
|
+
|
|
3191
|
+
assert.notCalled(publishStreamStub);
|
|
3192
|
+
});
|
|
3193
|
+
|
|
3194
|
+
it('should publish local streams when in the lobby and allowPublishMediaInLobby is true', async () => {
|
|
3195
|
+
const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
|
|
3196
|
+
|
|
3197
|
+
const audioSlot = {
|
|
3198
|
+
publishStream: sinon.stub(),
|
|
3199
|
+
unpublishStream: sinon.stub(),
|
|
3200
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3201
|
+
};
|
|
3202
|
+
const videoSlot = {
|
|
3203
|
+
publishStream: sinon.stub(),
|
|
3204
|
+
unpublishStream: sinon.stub(),
|
|
3205
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3206
|
+
};
|
|
3207
|
+
|
|
3208
|
+
fakeMediaConnection.createSendSlot = sinon.stub().callsFake((mediaType) => {
|
|
3209
|
+
if (mediaType === 'AUDIO-MAIN') {
|
|
3210
|
+
return audioSlot;
|
|
3211
|
+
}
|
|
3212
|
+
return videoSlot;
|
|
3213
|
+
});
|
|
3214
|
+
|
|
3215
|
+
await meeting.addMedia({
|
|
3216
|
+
allowMediaInLobby: true,
|
|
3217
|
+
allowPublishMediaInLobby: true,
|
|
3218
|
+
audioEnabled: true,
|
|
3219
|
+
videoEnabled: true,
|
|
3220
|
+
localStreams: {
|
|
3221
|
+
microphone: fakeMicrophoneStream,
|
|
3222
|
+
camera: fakeCameraStream,
|
|
3223
|
+
},
|
|
3224
|
+
});
|
|
3225
|
+
|
|
3226
|
+
assert.calledOnceWithExactly(audioSlot.publishStream, fakeMicrophoneStream);
|
|
3227
|
+
assert.calledOnceWithExactly(videoSlot.publishStream, fakeCameraStream);
|
|
3228
|
+
});
|
|
3229
|
+
|
|
3007
3230
|
it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
|
|
3008
3231
|
const setIntervalOriginal = window.setInterval;
|
|
3009
3232
|
window.setInterval = sinon.stub().returns(1);
|
|
@@ -6194,7 +6417,10 @@ describe('plugin-meetings', () => {
|
|
|
6194
6417
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
6195
6418
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
6196
6419
|
meeting.logger.error = sinon.stub().returns(true);
|
|
6197
|
-
meeting.
|
|
6420
|
+
meeting.clearMeetingData = sinon.stub().callsFake(async () => {
|
|
6421
|
+
meeting.audio = null;
|
|
6422
|
+
meeting.video = null;
|
|
6423
|
+
});
|
|
6198
6424
|
webex.internal.voicea.off = sinon.stub().returns(true);
|
|
6199
6425
|
meeting.stopTranscription = sinon.stub();
|
|
6200
6426
|
meeting.transcription = {};
|
|
@@ -6221,9 +6447,7 @@ describe('plugin-meetings', () => {
|
|
|
6221
6447
|
assert.calledOnce(meeting.closePeerConnections);
|
|
6222
6448
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
|
6223
6449
|
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);
|
|
6450
|
+
assert.calledOnce(meeting.clearMeetingData);
|
|
6227
6451
|
});
|
|
6228
6452
|
|
|
6229
6453
|
it('should reset call diagnostic latencies correctly', async () => {
|
|
@@ -8224,7 +8448,10 @@ describe('plugin-meetings', () => {
|
|
|
8224
8448
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
8225
8449
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
8226
8450
|
meeting.logger.error = sinon.stub().returns(true);
|
|
8227
|
-
meeting.
|
|
8451
|
+
meeting.clearMeetingData = sinon.stub().callsFake(async () => {
|
|
8452
|
+
meeting.audio = null;
|
|
8453
|
+
meeting.video = null;
|
|
8454
|
+
});
|
|
8228
8455
|
meeting.transcription = {};
|
|
8229
8456
|
meeting.stopTranscription = sinon.stub();
|
|
8230
8457
|
|
|
@@ -8250,10 +8477,7 @@ describe('plugin-meetings', () => {
|
|
|
8250
8477
|
assert.calledOnce(meeting?.closePeerConnections);
|
|
8251
8478
|
assert.calledOnce(meeting?.unsetRemoteStreams);
|
|
8252
8479
|
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);
|
|
8480
|
+
assert.calledOnce(meeting?.clearMeetingData);
|
|
8257
8481
|
});
|
|
8258
8482
|
});
|
|
8259
8483
|
|
|
@@ -9125,7 +9349,10 @@ describe('plugin-meetings', () => {
|
|
|
9125
9349
|
|
|
9126
9350
|
// check that the right things were called by the callback
|
|
9127
9351
|
assert.calledOnceWithExactly(meeting.waitForRemoteSDPAnswer);
|
|
9128
|
-
assert.calledOnceWithExactly(
|
|
9352
|
+
assert.calledOnceWithExactly(
|
|
9353
|
+
meeting.mediaProperties.waitForMediaConnectionConnected,
|
|
9354
|
+
meeting.correlationId
|
|
9355
|
+
);
|
|
9129
9356
|
});
|
|
9130
9357
|
});
|
|
9131
9358
|
|
|
@@ -10299,6 +10526,21 @@ describe('plugin-meetings', () => {
|
|
|
10299
10526
|
EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
|
|
10300
10527
|
);
|
|
10301
10528
|
});
|
|
10529
|
+
|
|
10530
|
+
it('listens to the self id changed event and updates aiEnableRequest', () => {
|
|
10531
|
+
meeting.aiEnableRequest = {
|
|
10532
|
+
selfParticipantIdUpdate: sinon.stub(),
|
|
10533
|
+
};
|
|
10534
|
+
|
|
10535
|
+
const payload = {selfId: 'participant-test-123'};
|
|
10536
|
+
|
|
10537
|
+
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ID_CHANGED', payload);
|
|
10538
|
+
|
|
10539
|
+
assert.calledOnceWithExactly(
|
|
10540
|
+
meeting.aiEnableRequest.selfParticipantIdUpdate,
|
|
10541
|
+
payload.selfId
|
|
10542
|
+
);
|
|
10543
|
+
});
|
|
10302
10544
|
});
|
|
10303
10545
|
|
|
10304
10546
|
describe('#setUpBreakoutsListener', () => {
|
|
@@ -10546,6 +10788,24 @@ describe('plugin-meetings', () => {
|
|
|
10546
10788
|
);
|
|
10547
10789
|
});
|
|
10548
10790
|
|
|
10791
|
+
it('listens to MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED', async () => {
|
|
10792
|
+
const aiSummaryNotification = {example: 'value'};
|
|
10793
|
+
|
|
10794
|
+
await meeting.locusInfo.emitScoped(
|
|
10795
|
+
{function: 'test', file: 'test'},
|
|
10796
|
+
LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
10797
|
+
{aiSummaryNotification}
|
|
10798
|
+
);
|
|
10799
|
+
|
|
10800
|
+
assert.calledWith(
|
|
10801
|
+
TriggerProxy.trigger,
|
|
10802
|
+
meeting,
|
|
10803
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
10804
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
10805
|
+
{aiSummaryNotification}
|
|
10806
|
+
);
|
|
10807
|
+
});
|
|
10808
|
+
|
|
10549
10809
|
it('listens to MEETING_CONTROLS_MEETING_FULL_UPDATED', async () => {
|
|
10550
10810
|
const state = {example: 'value'};
|
|
10551
10811
|
|
|
@@ -10818,6 +11078,9 @@ describe('plugin-meetings', () => {
|
|
|
10818
11078
|
meeting.simultaneousInterpretation = {
|
|
10819
11079
|
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
10820
11080
|
};
|
|
11081
|
+
meeting.aiEnableRequest = {
|
|
11082
|
+
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
11083
|
+
};
|
|
10821
11084
|
|
|
10822
11085
|
meeting.locusInfo.emit(
|
|
10823
11086
|
{function: 'test', file: 'test'},
|
|
@@ -10837,6 +11100,10 @@ describe('plugin-meetings', () => {
|
|
|
10837
11100
|
meeting.simultaneousInterpretation.approvalUrlUpdate,
|
|
10838
11101
|
newLocusServices.services.approval.url
|
|
10839
11102
|
);
|
|
11103
|
+
assert.calledWith(
|
|
11104
|
+
meeting.aiEnableRequest.approvalUrlUpdate,
|
|
11105
|
+
newLocusServices.services.approval.url
|
|
11106
|
+
);
|
|
10840
11107
|
assert.calledOnce(meeting.recordingController.setSessionId);
|
|
10841
11108
|
done();
|
|
10842
11109
|
});
|
|
@@ -11242,6 +11509,41 @@ describe('plugin-meetings', () => {
|
|
|
11242
11509
|
});
|
|
11243
11510
|
});
|
|
11244
11511
|
|
|
11512
|
+
describe('localConstraintsChangeHandler', () => {
|
|
11513
|
+
it('calls updatePreferredBitrateKbps when not multistream', () => {
|
|
11514
|
+
meeting.isMultistream = false;
|
|
11515
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
11516
|
+
updatePreferredBitrateKbps: sinon.stub(),
|
|
11517
|
+
};
|
|
11518
|
+
|
|
11519
|
+
meeting.localConstraintsChangeHandler();
|
|
11520
|
+
|
|
11521
|
+
assert.calledOnce(
|
|
11522
|
+
meeting.mediaProperties.webrtcMediaConnection.updatePreferredBitrateKbps
|
|
11523
|
+
);
|
|
11524
|
+
});
|
|
11525
|
+
|
|
11526
|
+
it('does not call updatePreferredBitrateKbps when multistream', () => {
|
|
11527
|
+
meeting.isMultistream = true;
|
|
11528
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
11529
|
+
updatePreferredBitrateKbps: sinon.stub(),
|
|
11530
|
+
};
|
|
11531
|
+
|
|
11532
|
+
meeting.localConstraintsChangeHandler();
|
|
11533
|
+
|
|
11534
|
+
assert.notCalled(
|
|
11535
|
+
meeting.mediaProperties.webrtcMediaConnection.updatePreferredBitrateKbps
|
|
11536
|
+
);
|
|
11537
|
+
});
|
|
11538
|
+
|
|
11539
|
+
it('does not throw when webrtcMediaConnection is undefined', () => {
|
|
11540
|
+
meeting.isMultistream = false;
|
|
11541
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
11542
|
+
|
|
11543
|
+
assert.doesNotThrow(() => meeting.localConstraintsChangeHandler());
|
|
11544
|
+
});
|
|
11545
|
+
});
|
|
11546
|
+
|
|
11245
11547
|
describe('#parseMeetingInfo', () => {
|
|
11246
11548
|
const checkParseMeetingInfo = (expectedInfoToParse) => {
|
|
11247
11549
|
assert.equal(meeting.conversationUrl, expectedInfoToParse.conversationUrl);
|
|
@@ -11621,6 +11923,7 @@ describe('plugin-meetings', () => {
|
|
|
11621
11923
|
let canUnsetDisallowUnmuteSpy;
|
|
11622
11924
|
let canUserRaiseHandSpy;
|
|
11623
11925
|
let bothLeaveAndEndMeetingAvailableSpy;
|
|
11926
|
+
let requireHostEndMeetingBeforeLeaveSpy;
|
|
11624
11927
|
let canUserLowerAllHandsSpy;
|
|
11625
11928
|
let canUserLowerSomeoneElsesHandSpy;
|
|
11626
11929
|
let waitingForOthersToJoinSpy;
|
|
@@ -11632,6 +11935,8 @@ describe('plugin-meetings', () => {
|
|
|
11632
11935
|
let canMoveToLobbySpy;
|
|
11633
11936
|
let isSpokenLanguageAutoDetectionEnabledSpy;
|
|
11634
11937
|
let showAutoEndMeetingWarningSpy;
|
|
11938
|
+
let canAttendeeRequestAiAssistantEnabledSpy;
|
|
11939
|
+
let attendeeRequestAiAssistantDeclinedAllSpy;
|
|
11635
11940
|
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
|
11636
11941
|
|
|
11637
11942
|
beforeEach(() => {
|
|
@@ -11652,6 +11957,10 @@ describe('plugin-meetings', () => {
|
|
|
11652
11957
|
MeetingUtil,
|
|
11653
11958
|
'bothLeaveAndEndMeetingAvailable'
|
|
11654
11959
|
);
|
|
11960
|
+
requireHostEndMeetingBeforeLeaveSpy = sinon.spy(
|
|
11961
|
+
MeetingUtil,
|
|
11962
|
+
'requireHostEndMeetingBeforeLeave'
|
|
11963
|
+
);
|
|
11655
11964
|
canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
|
|
11656
11965
|
waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
|
|
11657
11966
|
canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
|
|
@@ -11668,12 +11977,22 @@ describe('plugin-meetings', () => {
|
|
|
11668
11977
|
MeetingUtil,
|
|
11669
11978
|
'isSpokenLanguageAutoDetectionEnabled'
|
|
11670
11979
|
);
|
|
11980
|
+
canAttendeeRequestAiAssistantEnabledSpy = sinon.spy(
|
|
11981
|
+
MeetingUtil,
|
|
11982
|
+
'canAttendeeRequestAiAssistantEnabled'
|
|
11983
|
+
);
|
|
11984
|
+
attendeeRequestAiAssistantDeclinedAllSpy = sinon.spy(
|
|
11985
|
+
MeetingUtil,
|
|
11986
|
+
'attendeeRequestAiAssistantDeclinedAll'
|
|
11987
|
+
);
|
|
11671
11988
|
});
|
|
11672
11989
|
|
|
11673
11990
|
afterEach(() => {
|
|
11674
11991
|
inMeetingActionsSetSpy.restore();
|
|
11675
11992
|
waitingForOthersToJoinSpy.restore();
|
|
11676
11993
|
showAutoEndMeetingWarningSpy.restore();
|
|
11994
|
+
canAttendeeRequestAiAssistantEnabledSpy.restore();
|
|
11995
|
+
attendeeRequestAiAssistantDeclinedAllSpy.restore();
|
|
11677
11996
|
});
|
|
11678
11997
|
|
|
11679
11998
|
forEach(
|
|
@@ -12197,6 +12516,7 @@ describe('plugin-meetings', () => {
|
|
|
12197
12516
|
const userDisplayHints = ['LOCK_CONTROL_UNLOCK'];
|
|
12198
12517
|
meeting.userDisplayHints = ['LOCK_CONTROL_UNLOCK'];
|
|
12199
12518
|
meeting.meetingInfo.supportVoIP = true;
|
|
12519
|
+
meeting.roles = [];
|
|
12200
12520
|
|
|
12201
12521
|
meeting.updateMeetingActions();
|
|
12202
12522
|
|
|
@@ -12212,6 +12532,7 @@ describe('plugin-meetings', () => {
|
|
|
12212
12532
|
assert.calledWith(canUnsetDisallowUnmuteSpy, userDisplayHints);
|
|
12213
12533
|
assert.calledWith(canUserRaiseHandSpy, userDisplayHints);
|
|
12214
12534
|
assert.calledWith(bothLeaveAndEndMeetingAvailableSpy, userDisplayHints);
|
|
12535
|
+
assert.calledWith(requireHostEndMeetingBeforeLeaveSpy, userDisplayHints);
|
|
12215
12536
|
assert.calledWith(canUserLowerAllHandsSpy, userDisplayHints);
|
|
12216
12537
|
assert.calledWith(canUserLowerSomeoneElsesHandSpy, userDisplayHints);
|
|
12217
12538
|
assert.calledWith(waitingForOthersToJoinSpy, userDisplayHints);
|
|
@@ -12223,6 +12544,12 @@ describe('plugin-meetings', () => {
|
|
|
12223
12544
|
assert.calledWith(canMoveToLobbySpy, userDisplayHints);
|
|
12224
12545
|
assert.calledWith(showAutoEndMeetingWarningSpy, userDisplayHints);
|
|
12225
12546
|
assert.calledWith(isSpokenLanguageAutoDetectionEnabledSpy, userDisplayHints);
|
|
12547
|
+
assert.calledWith(
|
|
12548
|
+
canAttendeeRequestAiAssistantEnabledSpy,
|
|
12549
|
+
userDisplayHints,
|
|
12550
|
+
meeting.roles
|
|
12551
|
+
);
|
|
12552
|
+
assert.calledWith(attendeeRequestAiAssistantDeclinedAllSpy, userDisplayHints);
|
|
12226
12553
|
|
|
12227
12554
|
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
12228
12555
|
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
@@ -12365,33 +12692,159 @@ describe('plugin-meetings', () => {
|
|
|
12365
12692
|
|
|
12366
12693
|
describe('#handleDataChannelUrlChange', () => {
|
|
12367
12694
|
let updateLLMConnectionSpy;
|
|
12695
|
+
let updatePSDataChannelSpy;
|
|
12368
12696
|
|
|
12369
12697
|
beforeEach(() => {
|
|
12370
12698
|
updateLLMConnectionSpy = sinon.spy(meeting, 'updateLLMConnection');
|
|
12699
|
+
updatePSDataChannelSpy = sinon.stub(meeting.webinar, 'updatePSDataChannel').resolves();
|
|
12700
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
12371
12701
|
});
|
|
12372
12702
|
|
|
12373
|
-
const check = (
|
|
12374
|
-
|
|
12703
|
+
const check = (
|
|
12704
|
+
url,
|
|
12705
|
+
practiceSessionDatachannelUrl,
|
|
12706
|
+
{expectedMainCalled, expectedPracticeCalled}
|
|
12707
|
+
) => {
|
|
12708
|
+
meeting.handleDataChannelUrlChange(url, practiceSessionDatachannelUrl);
|
|
12375
12709
|
|
|
12376
|
-
if (
|
|
12710
|
+
if (expectedMainCalled) {
|
|
12377
12711
|
assert.calledWith(updateLLMConnectionSpy);
|
|
12378
12712
|
} else {
|
|
12379
12713
|
assert.notCalled(updateLLMConnectionSpy);
|
|
12380
12714
|
}
|
|
12715
|
+
|
|
12716
|
+
if (expectedPracticeCalled) {
|
|
12717
|
+
assert.calledWith(updatePSDataChannelSpy);
|
|
12718
|
+
} else {
|
|
12719
|
+
assert.notCalled(updatePSDataChannelSpy);
|
|
12720
|
+
}
|
|
12381
12721
|
};
|
|
12382
12722
|
|
|
12383
12723
|
it('calls deferred updateLLMConnection if datachannelURL is set and the enableAutomaticLLM is true', () => {
|
|
12384
12724
|
meeting.config.enableAutomaticLLM = true;
|
|
12385
|
-
check('some url', true);
|
|
12725
|
+
check('some url', undefined, {expectedMainCalled: true, expectedPracticeCalled: false});
|
|
12386
12726
|
});
|
|
12387
12727
|
|
|
12388
12728
|
it('does not call updateLLMConnection if datachannelURL is undefined', () => {
|
|
12389
12729
|
meeting.config.enableAutomaticLLM = true;
|
|
12390
|
-
check(undefined,
|
|
12730
|
+
check(undefined, undefined, {
|
|
12731
|
+
expectedMainCalled: false,
|
|
12732
|
+
expectedPracticeCalled: false,
|
|
12733
|
+
});
|
|
12391
12734
|
});
|
|
12392
12735
|
|
|
12393
12736
|
it('does not call updateLLMConnection if enableAutomaticLLM is false', () => {
|
|
12394
|
-
check('some url',
|
|
12737
|
+
check('some url', 'some practice url', {
|
|
12738
|
+
expectedMainCalled: false,
|
|
12739
|
+
expectedPracticeCalled: false,
|
|
12740
|
+
});
|
|
12741
|
+
});
|
|
12742
|
+
|
|
12743
|
+
it('calls updatePSDataChannel when practice-session routing is active', () => {
|
|
12744
|
+
meeting.config.enableAutomaticLLM = true;
|
|
12745
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
12746
|
+
|
|
12747
|
+
check('some url', 'some practice url', {
|
|
12748
|
+
expectedMainCalled: true,
|
|
12749
|
+
expectedPracticeCalled: true,
|
|
12750
|
+
});
|
|
12751
|
+
});
|
|
12752
|
+
|
|
12753
|
+
it('does not call updatePSDataChannel when the main datachannelURL is undefined', () => {
|
|
12754
|
+
meeting.config.enableAutomaticLLM = true;
|
|
12755
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
12756
|
+
|
|
12757
|
+
check(undefined, 'some practice url', {
|
|
12758
|
+
expectedMainCalled: false,
|
|
12759
|
+
expectedPracticeCalled: false,
|
|
12760
|
+
});
|
|
12761
|
+
});
|
|
12762
|
+
});
|
|
12763
|
+
|
|
12764
|
+
describe('#saveDataChannelToken', () => {
|
|
12765
|
+
beforeEach(() => {
|
|
12766
|
+
webex.internal.llm.setDatachannelToken = sinon.stub();
|
|
12767
|
+
});
|
|
12768
|
+
|
|
12769
|
+
it('saves datachannelToken into LLM as Default', () => {
|
|
12770
|
+
meeting.saveDataChannelToken({
|
|
12771
|
+
locus: {
|
|
12772
|
+
self: {datachannelToken: 'default-token'},
|
|
12773
|
+
},
|
|
12774
|
+
});
|
|
12775
|
+
|
|
12776
|
+
assert.calledWithExactly(
|
|
12777
|
+
webex.internal.llm.setDatachannelToken,
|
|
12778
|
+
'default-token',
|
|
12779
|
+
'llm-default-session'
|
|
12780
|
+
);
|
|
12781
|
+
});
|
|
12782
|
+
|
|
12783
|
+
it('saves practiceSessionDatachannelToken into LLM as PracticeSession', () => {
|
|
12784
|
+
meeting.saveDataChannelToken({
|
|
12785
|
+
locus: {
|
|
12786
|
+
self: {practiceSessionDatachannelToken: 'ps-token'},
|
|
12787
|
+
},
|
|
12788
|
+
});
|
|
12789
|
+
|
|
12790
|
+
assert.calledWithExactly(
|
|
12791
|
+
webex.internal.llm.setDatachannelToken,
|
|
12792
|
+
'ps-token',
|
|
12793
|
+
'llm-practice-session'
|
|
12794
|
+
);
|
|
12795
|
+
});
|
|
12796
|
+
|
|
12797
|
+
it('saves both tokens when both are present', () => {
|
|
12798
|
+
meeting.saveDataChannelToken({
|
|
12799
|
+
locus: {
|
|
12800
|
+
self: {
|
|
12801
|
+
datachannelToken: 'default-token',
|
|
12802
|
+
practiceSessionDatachannelToken: 'ps-token',
|
|
12803
|
+
},
|
|
12804
|
+
},
|
|
12805
|
+
});
|
|
12806
|
+
|
|
12807
|
+
assert.calledTwice(webex.internal.llm.setDatachannelToken);
|
|
12808
|
+
assert.calledWithExactly(
|
|
12809
|
+
webex.internal.llm.setDatachannelToken,
|
|
12810
|
+
'default-token',
|
|
12811
|
+
'llm-default-session'
|
|
12812
|
+
);
|
|
12813
|
+
assert.calledWithExactly(
|
|
12814
|
+
webex.internal.llm.setDatachannelToken,
|
|
12815
|
+
'ps-token',
|
|
12816
|
+
'llm-practice-session'
|
|
12817
|
+
);
|
|
12818
|
+
});
|
|
12819
|
+
|
|
12820
|
+
it('does not call setDatachannelToken when no tokens are present', () => {
|
|
12821
|
+
meeting.saveDataChannelToken({locus: {self: {}}});
|
|
12822
|
+
|
|
12823
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12824
|
+
});
|
|
12825
|
+
|
|
12826
|
+
it('handles undefined join gracefully', () => {
|
|
12827
|
+
meeting.saveDataChannelToken(undefined);
|
|
12828
|
+
|
|
12829
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12830
|
+
});
|
|
12831
|
+
|
|
12832
|
+
it('handles missing locus.self gracefully', () => {
|
|
12833
|
+
meeting.saveDataChannelToken({locus: {}});
|
|
12834
|
+
|
|
12835
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12836
|
+
});
|
|
12837
|
+
});
|
|
12838
|
+
|
|
12839
|
+
describe('#clearDataChannelToken', () => {
|
|
12840
|
+
beforeEach(() => {
|
|
12841
|
+
webex.internal.llm.resetDatachannelTokens = sinon.stub();
|
|
12842
|
+
});
|
|
12843
|
+
|
|
12844
|
+
it('calls resetDatachannelTokens on LLM', () => {
|
|
12845
|
+
meeting.clearDataChannelToken();
|
|
12846
|
+
|
|
12847
|
+
assert.calledOnce(webex.internal.llm.resetDatachannelTokens);
|
|
12395
12848
|
});
|
|
12396
12849
|
});
|
|
12397
12850
|
|
|
@@ -12400,16 +12853,20 @@ describe('plugin-meetings', () => {
|
|
|
12400
12853
|
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
12401
12854
|
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
12402
12855
|
webex.internal.llm.getDatachannelUrl = sinon.stub();
|
|
12403
|
-
webex.internal.llm.registerAndConnect = sinon
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
webex.internal.llm.
|
|
12407
|
-
|
|
12408
|
-
|
|
12856
|
+
webex.internal.llm.registerAndConnect = sinon.stub().resolves('something');
|
|
12857
|
+
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
12858
|
+
webex.internal.llm.on = sinon.stub();
|
|
12859
|
+
webex.internal.llm.off = sinon.stub();
|
|
12860
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
|
|
12861
|
+
webex.internal.llm.setDatachannelToken = sinon.stub();
|
|
12862
|
+
|
|
12409
12863
|
meeting.processRelayEvent = sinon.stub();
|
|
12864
|
+
meeting.processLocusLLMEvent = sinon.stub();
|
|
12865
|
+
meeting.clearLLMHealthCheckTimer = sinon.stub();
|
|
12866
|
+
meeting.startLLMHealthCheckTimer = sinon.stub();
|
|
12867
|
+
|
|
12410
12868
|
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
12411
12869
|
});
|
|
12412
|
-
|
|
12413
12870
|
it('does not connect if the call is not joined yet', async () => {
|
|
12414
12871
|
meeting.joinedWith = {state: 'any other state'};
|
|
12415
12872
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
@@ -12423,31 +12880,21 @@ describe('plugin-meetings', () => {
|
|
|
12423
12880
|
assert.equal(result, undefined);
|
|
12424
12881
|
assert.notCalled(meeting.webex.internal.llm.on);
|
|
12425
12882
|
});
|
|
12426
|
-
|
|
12427
12883
|
it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
|
|
12428
12884
|
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'}};
|
|
12885
|
+
meeting.locusInfo = {
|
|
12886
|
+
url: 'a url',
|
|
12887
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12888
|
+
};
|
|
12446
12889
|
|
|
12447
12890
|
const result = await meeting.updateLLMConnection();
|
|
12448
|
-
|
|
12449
12891
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12450
|
-
assert.
|
|
12892
|
+
assert.calledWithExactly(
|
|
12893
|
+
webex.internal.llm.registerAndConnect,
|
|
12894
|
+
'a url',
|
|
12895
|
+
'a datachannel url',
|
|
12896
|
+
undefined
|
|
12897
|
+
);
|
|
12451
12898
|
assert.equal(result, 'something');
|
|
12452
12899
|
assert.calledWithExactly(
|
|
12453
12900
|
meeting.webex.internal.llm.off,
|
|
@@ -12470,27 +12917,49 @@ describe('plugin-meetings', () => {
|
|
|
12470
12917
|
meeting.processLocusLLMEvent
|
|
12471
12918
|
);
|
|
12472
12919
|
});
|
|
12920
|
+
it('connects if not already connected', async () => {
|
|
12921
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
12922
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
12923
|
+
|
|
12924
|
+
const result = await meeting.updateLLMConnection();
|
|
12473
12925
|
|
|
12474
|
-
|
|
12926
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12927
|
+
assert.calledWithExactly(
|
|
12928
|
+
webex.internal.llm.registerAndConnect,
|
|
12929
|
+
'a url',
|
|
12930
|
+
'a datachannel url',
|
|
12931
|
+
undefined
|
|
12932
|
+
);
|
|
12933
|
+
assert.equal(result, 'something');
|
|
12934
|
+
});
|
|
12935
|
+
it('disconnects if the locus url has changed', async () => {
|
|
12475
12936
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12937
|
+
|
|
12476
12938
|
webex.internal.llm.isConnected.returns(true);
|
|
12477
12939
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12478
|
-
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
12479
12940
|
|
|
12480
|
-
meeting.locusInfo = {
|
|
12941
|
+
meeting.locusInfo = {
|
|
12942
|
+
url: 'a different url',
|
|
12943
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12944
|
+
self: {},
|
|
12945
|
+
};
|
|
12481
12946
|
|
|
12482
12947
|
const result = await meeting.updateLLMConnection();
|
|
12483
12948
|
|
|
12484
|
-
assert.
|
|
12949
|
+
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
12485
12950
|
code: 3050,
|
|
12486
12951
|
reason: 'done (permanent)',
|
|
12487
12952
|
});
|
|
12488
|
-
|
|
12953
|
+
|
|
12954
|
+
assert.calledWithExactly(
|
|
12489
12955
|
webex.internal.llm.registerAndConnect,
|
|
12490
12956
|
'a different url',
|
|
12491
|
-
'a datachannel url'
|
|
12957
|
+
'a datachannel url',
|
|
12958
|
+
undefined
|
|
12492
12959
|
);
|
|
12960
|
+
|
|
12493
12961
|
assert.equal(result, 'something');
|
|
12962
|
+
|
|
12494
12963
|
assert.calledWithExactly(
|
|
12495
12964
|
meeting.webex.internal.llm.off,
|
|
12496
12965
|
'event:relay.event',
|
|
@@ -12502,6 +12971,7 @@ describe('plugin-meetings', () => {
|
|
|
12502
12971
|
meeting.processLocusLLMEvent
|
|
12503
12972
|
);
|
|
12504
12973
|
assert.callCount(meeting.webex.internal.llm.off, 4);
|
|
12974
|
+
|
|
12505
12975
|
assert.calledWithExactly(
|
|
12506
12976
|
meeting.webex.internal.llm.on,
|
|
12507
12977
|
'event:relay.event',
|
|
@@ -12512,28 +12982,37 @@ describe('plugin-meetings', () => {
|
|
|
12512
12982
|
'event:locus.state_message',
|
|
12513
12983
|
meeting.processLocusLLMEvent
|
|
12514
12984
|
);
|
|
12985
|
+
assert.isFalse(
|
|
12986
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
12987
|
+
);
|
|
12515
12988
|
});
|
|
12516
|
-
|
|
12517
|
-
it('disconnects it first if the data channel url has changed', async () => {
|
|
12989
|
+
it('disconnects if the data channel url has changed', async () => {
|
|
12518
12990
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12519
12991
|
webex.internal.llm.isConnected.returns(true);
|
|
12520
12992
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12521
|
-
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
12522
12993
|
|
|
12523
|
-
meeting.locusInfo = {
|
|
12994
|
+
meeting.locusInfo = {
|
|
12995
|
+
url: 'a url',
|
|
12996
|
+
info: {datachannelUrl: 'a different datachannel url'},
|
|
12997
|
+
self: {},
|
|
12998
|
+
};
|
|
12524
12999
|
|
|
12525
13000
|
const result = await meeting.updateLLMConnection();
|
|
12526
13001
|
|
|
12527
|
-
assert.
|
|
13002
|
+
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
12528
13003
|
code: 3050,
|
|
12529
13004
|
reason: 'done (permanent)',
|
|
12530
13005
|
});
|
|
12531
|
-
|
|
13006
|
+
|
|
13007
|
+
assert.calledWithExactly(
|
|
12532
13008
|
webex.internal.llm.registerAndConnect,
|
|
12533
13009
|
'a url',
|
|
12534
|
-
'a different datachannel url'
|
|
13010
|
+
'a different datachannel url',
|
|
13011
|
+
undefined
|
|
12535
13012
|
);
|
|
13013
|
+
|
|
12536
13014
|
assert.equal(result, 'something');
|
|
13015
|
+
|
|
12537
13016
|
assert.calledWithExactly(
|
|
12538
13017
|
meeting.webex.internal.llm.off,
|
|
12539
13018
|
'event:relay.event',
|
|
@@ -12544,6 +13023,7 @@ describe('plugin-meetings', () => {
|
|
|
12544
13023
|
'event:locus.state_message',
|
|
12545
13024
|
meeting.processLocusLLMEvent
|
|
12546
13025
|
);
|
|
13026
|
+
|
|
12547
13027
|
assert.calledWithExactly(
|
|
12548
13028
|
meeting.webex.internal.llm.on,
|
|
12549
13029
|
'event:relay.event',
|
|
@@ -12554,8 +13034,10 @@ describe('plugin-meetings', () => {
|
|
|
12554
13034
|
'event:locus.state_message',
|
|
12555
13035
|
meeting.processLocusLLMEvent
|
|
12556
13036
|
);
|
|
13037
|
+
assert.isFalse(
|
|
13038
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13039
|
+
);
|
|
12557
13040
|
});
|
|
12558
|
-
|
|
12559
13041
|
it('disconnects when the state is not JOINED', async () => {
|
|
12560
13042
|
meeting.joinedWith = {state: 'any other state'};
|
|
12561
13043
|
webex.internal.llm.isConnected.returns(true);
|
|
@@ -12565,9 +13047,38 @@ describe('plugin-meetings', () => {
|
|
|
12565
13047
|
|
|
12566
13048
|
const result = await meeting.updateLLMConnection();
|
|
12567
13049
|
|
|
12568
|
-
assert.calledWith(webex.internal.llm.disconnectLLM,
|
|
13050
|
+
assert.calledWith(webex.internal.llm.disconnectLLM, {
|
|
13051
|
+
code: 3050,
|
|
13052
|
+
reason: 'done (permanent)',
|
|
13053
|
+
});
|
|
12569
13054
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12570
13055
|
assert.equal(result, undefined);
|
|
13056
|
+
assert.isFalse(
|
|
13057
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13058
|
+
);
|
|
13059
|
+
});
|
|
13060
|
+
it('rethrows disconnect errors during reconnect cleanup after removing relay listeners and timer', async () => {
|
|
13061
|
+
const disconnectError = new Error('disconnect failed');
|
|
13062
|
+
|
|
13063
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13064
|
+
webex.internal.llm.isConnected.returns(true);
|
|
13065
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
13066
|
+
webex.internal.llm.disconnectLLM.rejects(disconnectError);
|
|
13067
|
+
|
|
13068
|
+
meeting.locusInfo = {
|
|
13069
|
+
url: 'a different url',
|
|
13070
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13071
|
+
self: {},
|
|
13072
|
+
};
|
|
13073
|
+
|
|
13074
|
+
try {
|
|
13075
|
+
await meeting.updateLLMConnection();
|
|
13076
|
+
assert.fail('Expected updateLLMConnection to reject when disconnectLLM fails');
|
|
13077
|
+
} catch (error) {
|
|
13078
|
+
assert.equal(error, disconnectError);
|
|
13079
|
+
}
|
|
13080
|
+
|
|
13081
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12571
13082
|
assert.calledWithExactly(
|
|
12572
13083
|
meeting.webex.internal.llm.off,
|
|
12573
13084
|
'event:relay.event',
|
|
@@ -12578,22 +13089,159 @@ describe('plugin-meetings', () => {
|
|
|
12578
13089
|
'event:locus.state_message',
|
|
12579
13090
|
meeting.processLocusLLMEvent
|
|
12580
13091
|
);
|
|
13092
|
+
assert.isFalse(
|
|
13093
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13094
|
+
);
|
|
13095
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
12581
13096
|
});
|
|
12582
|
-
|
|
12583
|
-
it('connect ps data channel if ps started in webinar', async () => {
|
|
13097
|
+
it('still need connect main session data channel when PS started', async () => {
|
|
12584
13098
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12585
13099
|
meeting.locusInfo = {
|
|
12586
13100
|
url: 'a url',
|
|
12587
13101
|
info: {
|
|
12588
13102
|
datachannelUrl: 'a datachannel url',
|
|
12589
|
-
practiceSessionDatachannelUrl: '
|
|
13103
|
+
practiceSessionDatachannelUrl: 'ps-url',
|
|
12590
13104
|
},
|
|
12591
13105
|
};
|
|
12592
|
-
meeting.webinar.isJoinPracticeSessionDataChannel
|
|
13106
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
13107
|
+
|
|
12593
13108
|
await meeting.updateLLMConnection();
|
|
12594
13109
|
|
|
12595
|
-
assert.
|
|
12596
|
-
|
|
13110
|
+
assert.calledWithExactly(
|
|
13111
|
+
webex.internal.llm.registerAndConnect,
|
|
13112
|
+
'a url',
|
|
13113
|
+
'a datachannel url',
|
|
13114
|
+
undefined
|
|
13115
|
+
);
|
|
13116
|
+
});
|
|
13117
|
+
it('passes dataChannelToken from LLM to registerAndConnect', async () => {
|
|
13118
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13119
|
+
meeting.locusInfo = {
|
|
13120
|
+
url: 'a url',
|
|
13121
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13122
|
+
};
|
|
13123
|
+
|
|
13124
|
+
webex.internal.llm.getDatachannelToken.withArgs('llm-default-session').returns('token-123');
|
|
13125
|
+
|
|
13126
|
+
await meeting.updateLLMConnection();
|
|
13127
|
+
|
|
13128
|
+
assert.calledWithExactly(
|
|
13129
|
+
webex.internal.llm.registerAndConnect,
|
|
13130
|
+
'a url',
|
|
13131
|
+
'a datachannel url',
|
|
13132
|
+
'token-123'
|
|
13133
|
+
);
|
|
13134
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13135
|
+
});
|
|
13136
|
+
it('passes undefined token when LLM has no token stored', async () => {
|
|
13137
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13138
|
+
meeting.locusInfo = {
|
|
13139
|
+
url: 'a url',
|
|
13140
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13141
|
+
};
|
|
13142
|
+
|
|
13143
|
+
webex.internal.llm.getDatachannelToken.returns(undefined);
|
|
13144
|
+
|
|
13145
|
+
await meeting.updateLLMConnection();
|
|
13146
|
+
|
|
13147
|
+
assert.calledWithExactly(
|
|
13148
|
+
webex.internal.llm.registerAndConnect,
|
|
13149
|
+
'a url',
|
|
13150
|
+
'a datachannel url',
|
|
13151
|
+
undefined
|
|
13152
|
+
);
|
|
13153
|
+
|
|
13154
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13155
|
+
});
|
|
13156
|
+
|
|
13157
|
+
it('does not pass token when data channel with jwt token is disabled', async () => {
|
|
13158
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13159
|
+
meeting.locusInfo = {
|
|
13160
|
+
url: 'a url',
|
|
13161
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13162
|
+
};
|
|
13163
|
+
|
|
13164
|
+
webex.internal.llm.getDatachannelToken.returns(undefined);
|
|
13165
|
+
webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
|
|
13166
|
+
|
|
13167
|
+
await meeting.updateLLMConnection();
|
|
13168
|
+
|
|
13169
|
+
assert.calledWithExactly(
|
|
13170
|
+
webex.internal.llm.registerAndConnect,
|
|
13171
|
+
'a url',
|
|
13172
|
+
'a datachannel url',
|
|
13173
|
+
undefined
|
|
13174
|
+
);
|
|
13175
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13176
|
+
});
|
|
13177
|
+
|
|
13178
|
+
describe('#clearMeetingData', () => {
|
|
13179
|
+
beforeEach(() => {
|
|
13180
|
+
webex.internal.llm.isConnected = sinon.stub().returns(true);
|
|
13181
|
+
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
13182
|
+
webex.internal.llm.off = sinon.stub();
|
|
13183
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
13184
|
+
meeting.clearLLMHealthCheckTimer = sinon.stub();
|
|
13185
|
+
meeting.stopTranscription = sinon.stub();
|
|
13186
|
+
meeting.clearDataChannelToken = sinon.stub();
|
|
13187
|
+
meeting.shareStatus = 'no-share';
|
|
13188
|
+
});
|
|
13189
|
+
|
|
13190
|
+
it('disconnects llm and removes online and relay listeners during meeting data cleanup', async () => {
|
|
13191
|
+
await meeting.clearMeetingData();
|
|
13192
|
+
|
|
13193
|
+
assert.calledOnceWithExactly(webex.internal.llm.disconnectLLM, {
|
|
13194
|
+
code: 3050,
|
|
13195
|
+
reason: 'done (permanent)',
|
|
13196
|
+
});
|
|
13197
|
+
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
13198
|
+
assert.calledWithExactly(
|
|
13199
|
+
webex.internal.llm.off,
|
|
13200
|
+
'event:relay.event',
|
|
13201
|
+
meeting.processRelayEvent
|
|
13202
|
+
);
|
|
13203
|
+
assert.calledWithExactly(
|
|
13204
|
+
webex.internal.llm.off,
|
|
13205
|
+
'event:locus.state_message',
|
|
13206
|
+
meeting.processLocusLLMEvent
|
|
13207
|
+
);
|
|
13208
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13209
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13210
|
+
assert.isUndefined(meeting.transcription);
|
|
13211
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13212
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13213
|
+
});
|
|
13214
|
+
it('continues cleanup when disconnectLLM fails during meeting data cleanup', async () => {
|
|
13215
|
+
webex.internal.llm.disconnectLLM.rejects(new Error('disconnect failed'));
|
|
13216
|
+
|
|
13217
|
+
await meeting.clearMeetingData();
|
|
13218
|
+
|
|
13219
|
+
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
13220
|
+
assert.calledWithExactly(
|
|
13221
|
+
webex.internal.llm.off,
|
|
13222
|
+
'event:relay.event',
|
|
13223
|
+
meeting.processRelayEvent
|
|
13224
|
+
);
|
|
13225
|
+
assert.calledWithExactly(
|
|
13226
|
+
webex.internal.llm.off,
|
|
13227
|
+
'event:locus.state_message',
|
|
13228
|
+
meeting.processLocusLLMEvent
|
|
13229
|
+
);
|
|
13230
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13231
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13232
|
+
assert.isUndefined(meeting.transcription);
|
|
13233
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13234
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13235
|
+
});
|
|
13236
|
+
it('always calls stopTranscription even when transcription is undefined', async () => {
|
|
13237
|
+
meeting.transcription = undefined;
|
|
13238
|
+
|
|
13239
|
+
await meeting.clearMeetingData();
|
|
13240
|
+
|
|
13241
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13242
|
+
assert.isUndefined(meeting.transcription);
|
|
13243
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13244
|
+
});
|
|
12597
13245
|
});
|
|
12598
13246
|
});
|
|
12599
13247
|
|
|
@@ -12604,6 +13252,7 @@ describe('plugin-meetings', () => {
|
|
|
12604
13252
|
|
|
12605
13253
|
it('should read the locus object, set on the meeting and return null', () => {
|
|
12606
13254
|
const dataSets = {someFakeStuff: 'dataSet'};
|
|
13255
|
+
const metadata = {some: 'metadata'};
|
|
12607
13256
|
|
|
12608
13257
|
meeting.setLocus({
|
|
12609
13258
|
mediaConnections: [test1],
|
|
@@ -12613,12 +13262,14 @@ describe('plugin-meetings', () => {
|
|
|
12613
13262
|
mediaId: uuid3,
|
|
12614
13263
|
locus: {host: {id: uuid4}},
|
|
12615
13264
|
dataSets,
|
|
13265
|
+
metadata,
|
|
12616
13266
|
});
|
|
12617
13267
|
assert.calledOnce(meeting.locusInfo.initialSetup);
|
|
12618
13268
|
assert.calledWith(meeting.locusInfo.initialSetup, {
|
|
12619
13269
|
trigger: 'join-response',
|
|
12620
13270
|
locus: {host: {id: uuid4}},
|
|
12621
13271
|
dataSets,
|
|
13272
|
+
metadata,
|
|
12622
13273
|
});
|
|
12623
13274
|
assert.equal(meeting.mediaConnections, test1);
|
|
12624
13275
|
assert.equal(meeting.locusUrl, url1);
|
|
@@ -14160,6 +14811,69 @@ describe('plugin-meetings', () => {
|
|
|
14160
14811
|
assert.calledOnce(meeting.meetingRequest.keepAlive);
|
|
14161
14812
|
});
|
|
14162
14813
|
});
|
|
14814
|
+
describe('#refreshDataChannelToken()', () => {
|
|
14815
|
+
let meeting;
|
|
14816
|
+
|
|
14817
|
+
beforeEach(() => {
|
|
14818
|
+
meeting = Object.create(Meeting.prototype);
|
|
14819
|
+
meeting.locusUrl = 'https://locus.example.com';
|
|
14820
|
+
meeting.meetingRequest = {
|
|
14821
|
+
fetchDatachannelToken: sinon.stub().resolves({
|
|
14822
|
+
body: {datachannelToken: 'mock-token'},
|
|
14823
|
+
}),
|
|
14824
|
+
};
|
|
14825
|
+
meeting.members = {
|
|
14826
|
+
selfId: 'self-123',
|
|
14827
|
+
};
|
|
14828
|
+
meeting.webinar = {
|
|
14829
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
|
|
14830
|
+
};
|
|
14831
|
+
});
|
|
14832
|
+
|
|
14833
|
+
it('calls fetchDatachannelToken with correct parameters', async () => {
|
|
14834
|
+
await meeting.refreshDataChannelToken();
|
|
14835
|
+
|
|
14836
|
+
sinon.assert.calledOnce(meeting.meetingRequest.fetchDatachannelToken);
|
|
14837
|
+
|
|
14838
|
+
sinon.assert.calledWith(meeting.meetingRequest.fetchDatachannelToken, {
|
|
14839
|
+
locusUrl: 'https://locus.example.com',
|
|
14840
|
+
requestingParticipantId: 'self-123',
|
|
14841
|
+
isPracticeSession: true,
|
|
14842
|
+
});
|
|
14843
|
+
});
|
|
14844
|
+
|
|
14845
|
+
it('returns the correct structured result', async () => {
|
|
14846
|
+
const result = await meeting.refreshDataChannelToken();
|
|
14847
|
+
|
|
14848
|
+
expect(result).to.deep.equal({
|
|
14849
|
+
body: {
|
|
14850
|
+
datachannelToken: 'mock-token',
|
|
14851
|
+
dataChannelTokenType: 'llm-practice-session',
|
|
14852
|
+
},
|
|
14853
|
+
});
|
|
14854
|
+
});
|
|
14855
|
+
});
|
|
14856
|
+
describe('#getDataChannelTokenType', () => {
|
|
14857
|
+
it('returns PracticeSession when webinar is in practice session mode', () => {
|
|
14858
|
+
meeting.webinar = {
|
|
14859
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
|
|
14860
|
+
};
|
|
14861
|
+
|
|
14862
|
+
const result = meeting.getDataChannelTokenType();
|
|
14863
|
+
|
|
14864
|
+
expect(result).to.equal('llm-practice-session');
|
|
14865
|
+
});
|
|
14866
|
+
|
|
14867
|
+
it('returns Default when not in practice session mode', () => {
|
|
14868
|
+
meeting.webinar = {
|
|
14869
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(false),
|
|
14870
|
+
};
|
|
14871
|
+
|
|
14872
|
+
const result = meeting.getDataChannelTokenType();
|
|
14873
|
+
|
|
14874
|
+
expect(result).to.equal('llm-default-session');
|
|
14875
|
+
});
|
|
14876
|
+
});
|
|
14163
14877
|
describe('#stopKeepAlive', () => {
|
|
14164
14878
|
let clock;
|
|
14165
14879
|
const defaultKeepAliveUrl = 'keep.alive.url';
|