@webex/plugin-meetings 3.11.0 → 3.12.0
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 +5 -1
- 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 +709 -380
- 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 +217 -79
- 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 +1082 -861
- 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 +100 -45
- 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 +3 -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 +99 -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 +21 -2
- package/dist/types/locus-info/types.d.ts +1 -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 +38 -6
- 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 +260 -90
- 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 +3 -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 +627 -249
- 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 +231 -61
- package/src/locus-info/selfUtils.ts +1 -0
- package/src/locus-info/types.ts +1 -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 +205 -44
- 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 +135 -41
- 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 +162 -5
- 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 +1869 -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 +383 -46
- 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 +716 -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 +652 -31
- 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 +348 -36
- 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', () => {
|
|
@@ -1858,6 +1913,53 @@ describe('plugin-meetings', () => {
|
|
|
1858
1913
|
fakeProcessedReaction
|
|
1859
1914
|
);
|
|
1860
1915
|
});
|
|
1916
|
+
|
|
1917
|
+
it('should process if participantId does not exist in membersCollection but has displayName in Webinar', () => {
|
|
1918
|
+
LoggerProxy.logger.warn = sinon.stub();
|
|
1919
|
+
meeting.isReactionsSupported = sinon.stub().returns(true);
|
|
1920
|
+
meeting.config.receiveReactions = true;
|
|
1921
|
+
meeting.locusInfo.info = {isWebinar: true};
|
|
1922
|
+
const fakeSendersName = 'Fake reactors name';
|
|
1923
|
+
const fakeReactionPayload = {
|
|
1924
|
+
type: 'fake_type',
|
|
1925
|
+
codepoints: 'fake_codepoints',
|
|
1926
|
+
shortcodes: 'fake_shortcodes',
|
|
1927
|
+
tone: {
|
|
1928
|
+
type: 'fake_tone_type',
|
|
1929
|
+
codepoints: 'fake_tone_codepoints',
|
|
1930
|
+
shortcodes: 'fake_tone_shortcodes',
|
|
1931
|
+
},
|
|
1932
|
+
};
|
|
1933
|
+
const fakeSenderPayload = {
|
|
1934
|
+
displayName: 'Fake reactors name',
|
|
1935
|
+
participantId: 'fake_participant_id',
|
|
1936
|
+
};
|
|
1937
|
+
const fakeProcessedReaction = {
|
|
1938
|
+
reaction: fakeReactionPayload,
|
|
1939
|
+
sender: {
|
|
1940
|
+
id: fakeSenderPayload.participantId,
|
|
1941
|
+
name: fakeSendersName,
|
|
1942
|
+
},
|
|
1943
|
+
};
|
|
1944
|
+
const fakeRelayEvent = {
|
|
1945
|
+
data: {
|
|
1946
|
+
relayType: REACTION_RELAY_TYPES.REACTION,
|
|
1947
|
+
reaction: fakeReactionPayload,
|
|
1948
|
+
sender: fakeSenderPayload,
|
|
1949
|
+
},
|
|
1950
|
+
};
|
|
1951
|
+
meeting.processRelayEvent(fakeRelayEvent);
|
|
1952
|
+
assert.calledWith(
|
|
1953
|
+
TriggerProxy.trigger,
|
|
1954
|
+
sinon.match.instanceOf(Meeting),
|
|
1955
|
+
{
|
|
1956
|
+
file: 'meeting/index',
|
|
1957
|
+
function: 'join',
|
|
1958
|
+
},
|
|
1959
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
1960
|
+
fakeProcessedReaction
|
|
1961
|
+
);
|
|
1962
|
+
});
|
|
1861
1963
|
});
|
|
1862
1964
|
|
|
1863
1965
|
describe('#handleLLMOnline', () => {
|
|
@@ -3004,6 +3106,111 @@ describe('plugin-meetings', () => {
|
|
|
3004
3106
|
checkWorking({allowMediaInLobby: true});
|
|
3005
3107
|
});
|
|
3006
3108
|
|
|
3109
|
+
const setupLobbyTest = () => {
|
|
3110
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
3111
|
+
.stub()
|
|
3112
|
+
.resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
|
3113
|
+
|
|
3114
|
+
meeting.meetingState = 'ACTIVE';
|
|
3115
|
+
meeting.locusInfo.parsedLocus = {self: {state: 'IDLE'}};
|
|
3116
|
+
meeting.isUserUnadmitted = true;
|
|
3117
|
+
|
|
3118
|
+
// Mock locusMediaRequest
|
|
3119
|
+
meeting.locusMediaRequest = {
|
|
3120
|
+
send: sinon.stub().resolves(),
|
|
3121
|
+
isConfluenceCreated: sinon.stub().returns(false),
|
|
3122
|
+
};
|
|
3123
|
+
|
|
3124
|
+
sinon.stub(RemoteMediaManagerModule, 'RemoteMediaManager').returns({
|
|
3125
|
+
start: sinon.stub().resolves(),
|
|
3126
|
+
on: sinon.stub(),
|
|
3127
|
+
logAllReceiveSlots: sinon.stub(),
|
|
3128
|
+
});
|
|
3129
|
+
|
|
3130
|
+
meeting.isMultistream = true;
|
|
3131
|
+
|
|
3132
|
+
const createFakeStream = (id) => ({
|
|
3133
|
+
on: sinon.stub(),
|
|
3134
|
+
off: sinon.stub(),
|
|
3135
|
+
userMuted: false,
|
|
3136
|
+
systemMuted: false,
|
|
3137
|
+
get muted() {
|
|
3138
|
+
return this.userMuted || this.systemMuted;
|
|
3139
|
+
},
|
|
3140
|
+
setUnmuteAllowed: sinon.stub(),
|
|
3141
|
+
setUserMuted: sinon.stub(),
|
|
3142
|
+
outputStream: {
|
|
3143
|
+
getTracks: () => [{id}],
|
|
3144
|
+
},
|
|
3145
|
+
getSettings: sinon.stub().returns({}),
|
|
3146
|
+
});
|
|
3147
|
+
|
|
3148
|
+
return {
|
|
3149
|
+
fakeMicrophoneStream: createFakeStream('fake mic'),
|
|
3150
|
+
fakeCameraStream: createFakeStream('fake camera'),
|
|
3151
|
+
};
|
|
3152
|
+
};
|
|
3153
|
+
|
|
3154
|
+
it('should not publish any local streams when in the lobby and allowPublishMediaInLobby is false', async () => {
|
|
3155
|
+
const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
|
|
3156
|
+
|
|
3157
|
+
const publishStreamStub = sinon.stub();
|
|
3158
|
+
fakeMediaConnection.createSendSlot = sinon.stub().returns({
|
|
3159
|
+
publishStream: publishStreamStub,
|
|
3160
|
+
unpublishStream: sinon.stub(),
|
|
3161
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3162
|
+
});
|
|
3163
|
+
|
|
3164
|
+
await meeting.addMedia({
|
|
3165
|
+
allowMediaInLobby: true,
|
|
3166
|
+
allowPublishMediaInLobby: false,
|
|
3167
|
+
audioEnabled: true,
|
|
3168
|
+
videoEnabled: true,
|
|
3169
|
+
localStreams: {
|
|
3170
|
+
microphone: fakeMicrophoneStream,
|
|
3171
|
+
camera: fakeCameraStream,
|
|
3172
|
+
},
|
|
3173
|
+
});
|
|
3174
|
+
|
|
3175
|
+
assert.notCalled(publishStreamStub);
|
|
3176
|
+
});
|
|
3177
|
+
|
|
3178
|
+
it('should publish local streams when in the lobby and allowPublishMediaInLobby is true', async () => {
|
|
3179
|
+
const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
|
|
3180
|
+
|
|
3181
|
+
const audioSlot = {
|
|
3182
|
+
publishStream: sinon.stub(),
|
|
3183
|
+
unpublishStream: sinon.stub(),
|
|
3184
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3185
|
+
};
|
|
3186
|
+
const videoSlot = {
|
|
3187
|
+
publishStream: sinon.stub(),
|
|
3188
|
+
unpublishStream: sinon.stub(),
|
|
3189
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3190
|
+
};
|
|
3191
|
+
|
|
3192
|
+
fakeMediaConnection.createSendSlot = sinon.stub().callsFake((mediaType) => {
|
|
3193
|
+
if (mediaType === 'AUDIO-MAIN') {
|
|
3194
|
+
return audioSlot;
|
|
3195
|
+
}
|
|
3196
|
+
return videoSlot;
|
|
3197
|
+
});
|
|
3198
|
+
|
|
3199
|
+
await meeting.addMedia({
|
|
3200
|
+
allowMediaInLobby: true,
|
|
3201
|
+
allowPublishMediaInLobby: true,
|
|
3202
|
+
audioEnabled: true,
|
|
3203
|
+
videoEnabled: true,
|
|
3204
|
+
localStreams: {
|
|
3205
|
+
microphone: fakeMicrophoneStream,
|
|
3206
|
+
camera: fakeCameraStream,
|
|
3207
|
+
},
|
|
3208
|
+
});
|
|
3209
|
+
|
|
3210
|
+
assert.calledOnceWithExactly(audioSlot.publishStream, fakeMicrophoneStream);
|
|
3211
|
+
assert.calledOnceWithExactly(videoSlot.publishStream, fakeCameraStream);
|
|
3212
|
+
});
|
|
3213
|
+
|
|
3007
3214
|
it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
|
|
3008
3215
|
const setIntervalOriginal = window.setInterval;
|
|
3009
3216
|
window.setInterval = sinon.stub().returns(1);
|
|
@@ -6194,7 +6401,10 @@ describe('plugin-meetings', () => {
|
|
|
6194
6401
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
6195
6402
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
6196
6403
|
meeting.logger.error = sinon.stub().returns(true);
|
|
6197
|
-
meeting.
|
|
6404
|
+
meeting.clearMeetingData = sinon.stub().callsFake(async () => {
|
|
6405
|
+
meeting.audio = null;
|
|
6406
|
+
meeting.video = null;
|
|
6407
|
+
});
|
|
6198
6408
|
webex.internal.voicea.off = sinon.stub().returns(true);
|
|
6199
6409
|
meeting.stopTranscription = sinon.stub();
|
|
6200
6410
|
meeting.transcription = {};
|
|
@@ -6221,9 +6431,7 @@ describe('plugin-meetings', () => {
|
|
|
6221
6431
|
assert.calledOnce(meeting.closePeerConnections);
|
|
6222
6432
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
|
6223
6433
|
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);
|
|
6434
|
+
assert.calledOnce(meeting.clearMeetingData);
|
|
6227
6435
|
});
|
|
6228
6436
|
|
|
6229
6437
|
it('should reset call diagnostic latencies correctly', async () => {
|
|
@@ -8224,7 +8432,10 @@ describe('plugin-meetings', () => {
|
|
|
8224
8432
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
8225
8433
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
8226
8434
|
meeting.logger.error = sinon.stub().returns(true);
|
|
8227
|
-
meeting.
|
|
8435
|
+
meeting.clearMeetingData = sinon.stub().callsFake(async () => {
|
|
8436
|
+
meeting.audio = null;
|
|
8437
|
+
meeting.video = null;
|
|
8438
|
+
});
|
|
8228
8439
|
meeting.transcription = {};
|
|
8229
8440
|
meeting.stopTranscription = sinon.stub();
|
|
8230
8441
|
|
|
@@ -8250,10 +8461,7 @@ describe('plugin-meetings', () => {
|
|
|
8250
8461
|
assert.calledOnce(meeting?.closePeerConnections);
|
|
8251
8462
|
assert.calledOnce(meeting?.unsetRemoteStreams);
|
|
8252
8463
|
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);
|
|
8464
|
+
assert.calledOnce(meeting?.clearMeetingData);
|
|
8257
8465
|
});
|
|
8258
8466
|
});
|
|
8259
8467
|
|
|
@@ -9125,7 +9333,10 @@ describe('plugin-meetings', () => {
|
|
|
9125
9333
|
|
|
9126
9334
|
// check that the right things were called by the callback
|
|
9127
9335
|
assert.calledOnceWithExactly(meeting.waitForRemoteSDPAnswer);
|
|
9128
|
-
assert.calledOnceWithExactly(
|
|
9336
|
+
assert.calledOnceWithExactly(
|
|
9337
|
+
meeting.mediaProperties.waitForMediaConnectionConnected,
|
|
9338
|
+
meeting.correlationId
|
|
9339
|
+
);
|
|
9129
9340
|
});
|
|
9130
9341
|
});
|
|
9131
9342
|
|
|
@@ -10299,6 +10510,21 @@ describe('plugin-meetings', () => {
|
|
|
10299
10510
|
EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
|
|
10300
10511
|
);
|
|
10301
10512
|
});
|
|
10513
|
+
|
|
10514
|
+
it('listens to the self id changed event and updates aiEnableRequest', () => {
|
|
10515
|
+
meeting.aiEnableRequest = {
|
|
10516
|
+
selfParticipantIdUpdate: sinon.stub(),
|
|
10517
|
+
};
|
|
10518
|
+
|
|
10519
|
+
const payload = {selfId: 'participant-test-123'};
|
|
10520
|
+
|
|
10521
|
+
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ID_CHANGED', payload);
|
|
10522
|
+
|
|
10523
|
+
assert.calledOnceWithExactly(
|
|
10524
|
+
meeting.aiEnableRequest.selfParticipantIdUpdate,
|
|
10525
|
+
payload.selfId
|
|
10526
|
+
);
|
|
10527
|
+
});
|
|
10302
10528
|
});
|
|
10303
10529
|
|
|
10304
10530
|
describe('#setUpBreakoutsListener', () => {
|
|
@@ -10546,6 +10772,24 @@ describe('plugin-meetings', () => {
|
|
|
10546
10772
|
);
|
|
10547
10773
|
});
|
|
10548
10774
|
|
|
10775
|
+
it('listens to MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED', async () => {
|
|
10776
|
+
const aiSummaryNotification = {example: 'value'};
|
|
10777
|
+
|
|
10778
|
+
await meeting.locusInfo.emitScoped(
|
|
10779
|
+
{function: 'test', file: 'test'},
|
|
10780
|
+
LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
10781
|
+
{aiSummaryNotification}
|
|
10782
|
+
);
|
|
10783
|
+
|
|
10784
|
+
assert.calledWith(
|
|
10785
|
+
TriggerProxy.trigger,
|
|
10786
|
+
meeting,
|
|
10787
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
10788
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
10789
|
+
{aiSummaryNotification}
|
|
10790
|
+
);
|
|
10791
|
+
});
|
|
10792
|
+
|
|
10549
10793
|
it('listens to MEETING_CONTROLS_MEETING_FULL_UPDATED', async () => {
|
|
10550
10794
|
const state = {example: 'value'};
|
|
10551
10795
|
|
|
@@ -10818,6 +11062,9 @@ describe('plugin-meetings', () => {
|
|
|
10818
11062
|
meeting.simultaneousInterpretation = {
|
|
10819
11063
|
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
10820
11064
|
};
|
|
11065
|
+
meeting.aiEnableRequest = {
|
|
11066
|
+
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
11067
|
+
};
|
|
10821
11068
|
|
|
10822
11069
|
meeting.locusInfo.emit(
|
|
10823
11070
|
{function: 'test', file: 'test'},
|
|
@@ -10837,6 +11084,10 @@ describe('plugin-meetings', () => {
|
|
|
10837
11084
|
meeting.simultaneousInterpretation.approvalUrlUpdate,
|
|
10838
11085
|
newLocusServices.services.approval.url
|
|
10839
11086
|
);
|
|
11087
|
+
assert.calledWith(
|
|
11088
|
+
meeting.aiEnableRequest.approvalUrlUpdate,
|
|
11089
|
+
newLocusServices.services.approval.url
|
|
11090
|
+
);
|
|
10840
11091
|
assert.calledOnce(meeting.recordingController.setSessionId);
|
|
10841
11092
|
done();
|
|
10842
11093
|
});
|
|
@@ -11242,6 +11493,41 @@ describe('plugin-meetings', () => {
|
|
|
11242
11493
|
});
|
|
11243
11494
|
});
|
|
11244
11495
|
|
|
11496
|
+
describe('localConstraintsChangeHandler', () => {
|
|
11497
|
+
it('calls updatePreferredBitrateKbps when not multistream', () => {
|
|
11498
|
+
meeting.isMultistream = false;
|
|
11499
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
11500
|
+
updatePreferredBitrateKbps: sinon.stub(),
|
|
11501
|
+
};
|
|
11502
|
+
|
|
11503
|
+
meeting.localConstraintsChangeHandler();
|
|
11504
|
+
|
|
11505
|
+
assert.calledOnce(
|
|
11506
|
+
meeting.mediaProperties.webrtcMediaConnection.updatePreferredBitrateKbps
|
|
11507
|
+
);
|
|
11508
|
+
});
|
|
11509
|
+
|
|
11510
|
+
it('does not call updatePreferredBitrateKbps when multistream', () => {
|
|
11511
|
+
meeting.isMultistream = true;
|
|
11512
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
11513
|
+
updatePreferredBitrateKbps: sinon.stub(),
|
|
11514
|
+
};
|
|
11515
|
+
|
|
11516
|
+
meeting.localConstraintsChangeHandler();
|
|
11517
|
+
|
|
11518
|
+
assert.notCalled(
|
|
11519
|
+
meeting.mediaProperties.webrtcMediaConnection.updatePreferredBitrateKbps
|
|
11520
|
+
);
|
|
11521
|
+
});
|
|
11522
|
+
|
|
11523
|
+
it('does not throw when webrtcMediaConnection is undefined', () => {
|
|
11524
|
+
meeting.isMultistream = false;
|
|
11525
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
11526
|
+
|
|
11527
|
+
assert.doesNotThrow(() => meeting.localConstraintsChangeHandler());
|
|
11528
|
+
});
|
|
11529
|
+
});
|
|
11530
|
+
|
|
11245
11531
|
describe('#parseMeetingInfo', () => {
|
|
11246
11532
|
const checkParseMeetingInfo = (expectedInfoToParse) => {
|
|
11247
11533
|
assert.equal(meeting.conversationUrl, expectedInfoToParse.conversationUrl);
|
|
@@ -11621,6 +11907,7 @@ describe('plugin-meetings', () => {
|
|
|
11621
11907
|
let canUnsetDisallowUnmuteSpy;
|
|
11622
11908
|
let canUserRaiseHandSpy;
|
|
11623
11909
|
let bothLeaveAndEndMeetingAvailableSpy;
|
|
11910
|
+
let requireHostEndMeetingBeforeLeaveSpy;
|
|
11624
11911
|
let canUserLowerAllHandsSpy;
|
|
11625
11912
|
let canUserLowerSomeoneElsesHandSpy;
|
|
11626
11913
|
let waitingForOthersToJoinSpy;
|
|
@@ -11632,6 +11919,8 @@ describe('plugin-meetings', () => {
|
|
|
11632
11919
|
let canMoveToLobbySpy;
|
|
11633
11920
|
let isSpokenLanguageAutoDetectionEnabledSpy;
|
|
11634
11921
|
let showAutoEndMeetingWarningSpy;
|
|
11922
|
+
let canAttendeeRequestAiAssistantEnabledSpy;
|
|
11923
|
+
let attendeeRequestAiAssistantDeclinedAllSpy;
|
|
11635
11924
|
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
|
11636
11925
|
|
|
11637
11926
|
beforeEach(() => {
|
|
@@ -11652,6 +11941,10 @@ describe('plugin-meetings', () => {
|
|
|
11652
11941
|
MeetingUtil,
|
|
11653
11942
|
'bothLeaveAndEndMeetingAvailable'
|
|
11654
11943
|
);
|
|
11944
|
+
requireHostEndMeetingBeforeLeaveSpy = sinon.spy(
|
|
11945
|
+
MeetingUtil,
|
|
11946
|
+
'requireHostEndMeetingBeforeLeave'
|
|
11947
|
+
);
|
|
11655
11948
|
canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
|
|
11656
11949
|
waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
|
|
11657
11950
|
canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
|
|
@@ -11668,12 +11961,22 @@ describe('plugin-meetings', () => {
|
|
|
11668
11961
|
MeetingUtil,
|
|
11669
11962
|
'isSpokenLanguageAutoDetectionEnabled'
|
|
11670
11963
|
);
|
|
11964
|
+
canAttendeeRequestAiAssistantEnabledSpy = sinon.spy(
|
|
11965
|
+
MeetingUtil,
|
|
11966
|
+
'canAttendeeRequestAiAssistantEnabled'
|
|
11967
|
+
);
|
|
11968
|
+
attendeeRequestAiAssistantDeclinedAllSpy = sinon.spy(
|
|
11969
|
+
MeetingUtil,
|
|
11970
|
+
'attendeeRequestAiAssistantDeclinedAll'
|
|
11971
|
+
);
|
|
11671
11972
|
});
|
|
11672
11973
|
|
|
11673
11974
|
afterEach(() => {
|
|
11674
11975
|
inMeetingActionsSetSpy.restore();
|
|
11675
11976
|
waitingForOthersToJoinSpy.restore();
|
|
11676
11977
|
showAutoEndMeetingWarningSpy.restore();
|
|
11978
|
+
canAttendeeRequestAiAssistantEnabledSpy.restore();
|
|
11979
|
+
attendeeRequestAiAssistantDeclinedAllSpy.restore();
|
|
11677
11980
|
});
|
|
11678
11981
|
|
|
11679
11982
|
forEach(
|
|
@@ -12197,6 +12500,7 @@ describe('plugin-meetings', () => {
|
|
|
12197
12500
|
const userDisplayHints = ['LOCK_CONTROL_UNLOCK'];
|
|
12198
12501
|
meeting.userDisplayHints = ['LOCK_CONTROL_UNLOCK'];
|
|
12199
12502
|
meeting.meetingInfo.supportVoIP = true;
|
|
12503
|
+
meeting.roles = [];
|
|
12200
12504
|
|
|
12201
12505
|
meeting.updateMeetingActions();
|
|
12202
12506
|
|
|
@@ -12212,6 +12516,7 @@ describe('plugin-meetings', () => {
|
|
|
12212
12516
|
assert.calledWith(canUnsetDisallowUnmuteSpy, userDisplayHints);
|
|
12213
12517
|
assert.calledWith(canUserRaiseHandSpy, userDisplayHints);
|
|
12214
12518
|
assert.calledWith(bothLeaveAndEndMeetingAvailableSpy, userDisplayHints);
|
|
12519
|
+
assert.calledWith(requireHostEndMeetingBeforeLeaveSpy, userDisplayHints);
|
|
12215
12520
|
assert.calledWith(canUserLowerAllHandsSpy, userDisplayHints);
|
|
12216
12521
|
assert.calledWith(canUserLowerSomeoneElsesHandSpy, userDisplayHints);
|
|
12217
12522
|
assert.calledWith(waitingForOthersToJoinSpy, userDisplayHints);
|
|
@@ -12223,6 +12528,12 @@ describe('plugin-meetings', () => {
|
|
|
12223
12528
|
assert.calledWith(canMoveToLobbySpy, userDisplayHints);
|
|
12224
12529
|
assert.calledWith(showAutoEndMeetingWarningSpy, userDisplayHints);
|
|
12225
12530
|
assert.calledWith(isSpokenLanguageAutoDetectionEnabledSpy, userDisplayHints);
|
|
12531
|
+
assert.calledWith(
|
|
12532
|
+
canAttendeeRequestAiAssistantEnabledSpy,
|
|
12533
|
+
userDisplayHints,
|
|
12534
|
+
meeting.roles
|
|
12535
|
+
);
|
|
12536
|
+
assert.calledWith(attendeeRequestAiAssistantDeclinedAllSpy, userDisplayHints);
|
|
12226
12537
|
|
|
12227
12538
|
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
12228
12539
|
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
@@ -12365,33 +12676,72 @@ describe('plugin-meetings', () => {
|
|
|
12365
12676
|
|
|
12366
12677
|
describe('#handleDataChannelUrlChange', () => {
|
|
12367
12678
|
let updateLLMConnectionSpy;
|
|
12679
|
+
let updatePSDataChannelSpy;
|
|
12368
12680
|
|
|
12369
12681
|
beforeEach(() => {
|
|
12370
12682
|
updateLLMConnectionSpy = sinon.spy(meeting, 'updateLLMConnection');
|
|
12683
|
+
updatePSDataChannelSpy = sinon.stub(meeting.webinar, 'updatePSDataChannel').resolves();
|
|
12684
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
12371
12685
|
});
|
|
12372
12686
|
|
|
12373
|
-
const check = (
|
|
12374
|
-
|
|
12687
|
+
const check = (
|
|
12688
|
+
url,
|
|
12689
|
+
practiceSessionDatachannelUrl,
|
|
12690
|
+
{expectedMainCalled, expectedPracticeCalled}
|
|
12691
|
+
) => {
|
|
12692
|
+
meeting.handleDataChannelUrlChange(url, practiceSessionDatachannelUrl);
|
|
12375
12693
|
|
|
12376
|
-
if (
|
|
12694
|
+
if (expectedMainCalled) {
|
|
12377
12695
|
assert.calledWith(updateLLMConnectionSpy);
|
|
12378
12696
|
} else {
|
|
12379
12697
|
assert.notCalled(updateLLMConnectionSpy);
|
|
12380
12698
|
}
|
|
12699
|
+
|
|
12700
|
+
if (expectedPracticeCalled) {
|
|
12701
|
+
assert.calledWith(updatePSDataChannelSpy);
|
|
12702
|
+
} else {
|
|
12703
|
+
assert.notCalled(updatePSDataChannelSpy);
|
|
12704
|
+
}
|
|
12381
12705
|
};
|
|
12382
12706
|
|
|
12383
12707
|
it('calls deferred updateLLMConnection if datachannelURL is set and the enableAutomaticLLM is true', () => {
|
|
12384
12708
|
meeting.config.enableAutomaticLLM = true;
|
|
12385
|
-
check('some url', true);
|
|
12709
|
+
check('some url', undefined, {expectedMainCalled: true, expectedPracticeCalled: false});
|
|
12386
12710
|
});
|
|
12387
12711
|
|
|
12388
12712
|
it('does not call updateLLMConnection if datachannelURL is undefined', () => {
|
|
12389
12713
|
meeting.config.enableAutomaticLLM = true;
|
|
12390
|
-
check(undefined,
|
|
12714
|
+
check(undefined, undefined, {
|
|
12715
|
+
expectedMainCalled: false,
|
|
12716
|
+
expectedPracticeCalled: false,
|
|
12717
|
+
});
|
|
12391
12718
|
});
|
|
12392
12719
|
|
|
12393
12720
|
it('does not call updateLLMConnection if enableAutomaticLLM is false', () => {
|
|
12394
|
-
check('some url',
|
|
12721
|
+
check('some url', 'some practice url', {
|
|
12722
|
+
expectedMainCalled: false,
|
|
12723
|
+
expectedPracticeCalled: false,
|
|
12724
|
+
});
|
|
12725
|
+
});
|
|
12726
|
+
|
|
12727
|
+
it('calls updatePSDataChannel when practice-session routing is active', () => {
|
|
12728
|
+
meeting.config.enableAutomaticLLM = true;
|
|
12729
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
12730
|
+
|
|
12731
|
+
check('some url', 'some practice url', {
|
|
12732
|
+
expectedMainCalled: true,
|
|
12733
|
+
expectedPracticeCalled: true,
|
|
12734
|
+
});
|
|
12735
|
+
});
|
|
12736
|
+
|
|
12737
|
+
it('does not call updatePSDataChannel when the main datachannelURL is undefined', () => {
|
|
12738
|
+
meeting.config.enableAutomaticLLM = true;
|
|
12739
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
12740
|
+
|
|
12741
|
+
check(undefined, 'some practice url', {
|
|
12742
|
+
expectedMainCalled: false,
|
|
12743
|
+
expectedPracticeCalled: false,
|
|
12744
|
+
});
|
|
12395
12745
|
});
|
|
12396
12746
|
});
|
|
12397
12747
|
|
|
@@ -12400,16 +12750,20 @@ describe('plugin-meetings', () => {
|
|
|
12400
12750
|
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
12401
12751
|
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
12402
12752
|
webex.internal.llm.getDatachannelUrl = sinon.stub();
|
|
12403
|
-
webex.internal.llm.registerAndConnect = sinon
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
webex.internal.llm.
|
|
12407
|
-
|
|
12408
|
-
|
|
12753
|
+
webex.internal.llm.registerAndConnect = sinon.stub().resolves('something');
|
|
12754
|
+
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
12755
|
+
webex.internal.llm.on = sinon.stub();
|
|
12756
|
+
webex.internal.llm.off = sinon.stub();
|
|
12757
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
|
|
12758
|
+
webex.internal.llm.setDatachannelToken = sinon.stub();
|
|
12759
|
+
|
|
12409
12760
|
meeting.processRelayEvent = sinon.stub();
|
|
12761
|
+
meeting.processLocusLLMEvent = sinon.stub();
|
|
12762
|
+
meeting.clearLLMHealthCheckTimer = sinon.stub();
|
|
12763
|
+
meeting.startLLMHealthCheckTimer = sinon.stub();
|
|
12764
|
+
|
|
12410
12765
|
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
12411
12766
|
});
|
|
12412
|
-
|
|
12413
12767
|
it('does not connect if the call is not joined yet', async () => {
|
|
12414
12768
|
meeting.joinedWith = {state: 'any other state'};
|
|
12415
12769
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
@@ -12423,31 +12777,21 @@ describe('plugin-meetings', () => {
|
|
|
12423
12777
|
assert.equal(result, undefined);
|
|
12424
12778
|
assert.notCalled(meeting.webex.internal.llm.on);
|
|
12425
12779
|
});
|
|
12426
|
-
|
|
12427
12780
|
it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
|
|
12428
12781
|
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'}};
|
|
12782
|
+
meeting.locusInfo = {
|
|
12783
|
+
url: 'a url',
|
|
12784
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12785
|
+
};
|
|
12446
12786
|
|
|
12447
12787
|
const result = await meeting.updateLLMConnection();
|
|
12448
|
-
|
|
12449
12788
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12450
|
-
assert.
|
|
12789
|
+
assert.calledWithExactly(
|
|
12790
|
+
webex.internal.llm.registerAndConnect,
|
|
12791
|
+
'a url',
|
|
12792
|
+
'a datachannel url',
|
|
12793
|
+
undefined
|
|
12794
|
+
);
|
|
12451
12795
|
assert.equal(result, 'something');
|
|
12452
12796
|
assert.calledWithExactly(
|
|
12453
12797
|
meeting.webex.internal.llm.off,
|
|
@@ -12470,27 +12814,49 @@ describe('plugin-meetings', () => {
|
|
|
12470
12814
|
meeting.processLocusLLMEvent
|
|
12471
12815
|
);
|
|
12472
12816
|
});
|
|
12817
|
+
it('connects if not already connected', async () => {
|
|
12818
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
12819
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
12473
12820
|
|
|
12474
|
-
|
|
12821
|
+
const result = await meeting.updateLLMConnection();
|
|
12822
|
+
|
|
12823
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12824
|
+
assert.calledWithExactly(
|
|
12825
|
+
webex.internal.llm.registerAndConnect,
|
|
12826
|
+
'a url',
|
|
12827
|
+
'a datachannel url',
|
|
12828
|
+
undefined
|
|
12829
|
+
);
|
|
12830
|
+
assert.equal(result, 'something');
|
|
12831
|
+
});
|
|
12832
|
+
it('disconnects if the locus url has changed', async () => {
|
|
12475
12833
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12834
|
+
|
|
12476
12835
|
webex.internal.llm.isConnected.returns(true);
|
|
12477
12836
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12478
|
-
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
12479
12837
|
|
|
12480
|
-
meeting.locusInfo = {
|
|
12838
|
+
meeting.locusInfo = {
|
|
12839
|
+
url: 'a different url',
|
|
12840
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12841
|
+
self: {},
|
|
12842
|
+
};
|
|
12481
12843
|
|
|
12482
12844
|
const result = await meeting.updateLLMConnection();
|
|
12483
12845
|
|
|
12484
|
-
assert.
|
|
12846
|
+
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
12485
12847
|
code: 3050,
|
|
12486
12848
|
reason: 'done (permanent)',
|
|
12487
12849
|
});
|
|
12488
|
-
|
|
12850
|
+
|
|
12851
|
+
assert.calledWithExactly(
|
|
12489
12852
|
webex.internal.llm.registerAndConnect,
|
|
12490
12853
|
'a different url',
|
|
12491
|
-
'a datachannel url'
|
|
12854
|
+
'a datachannel url',
|
|
12855
|
+
undefined
|
|
12492
12856
|
);
|
|
12857
|
+
|
|
12493
12858
|
assert.equal(result, 'something');
|
|
12859
|
+
|
|
12494
12860
|
assert.calledWithExactly(
|
|
12495
12861
|
meeting.webex.internal.llm.off,
|
|
12496
12862
|
'event:relay.event',
|
|
@@ -12502,6 +12868,7 @@ describe('plugin-meetings', () => {
|
|
|
12502
12868
|
meeting.processLocusLLMEvent
|
|
12503
12869
|
);
|
|
12504
12870
|
assert.callCount(meeting.webex.internal.llm.off, 4);
|
|
12871
|
+
|
|
12505
12872
|
assert.calledWithExactly(
|
|
12506
12873
|
meeting.webex.internal.llm.on,
|
|
12507
12874
|
'event:relay.event',
|
|
@@ -12512,28 +12879,37 @@ describe('plugin-meetings', () => {
|
|
|
12512
12879
|
'event:locus.state_message',
|
|
12513
12880
|
meeting.processLocusLLMEvent
|
|
12514
12881
|
);
|
|
12882
|
+
assert.isFalse(
|
|
12883
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
12884
|
+
);
|
|
12515
12885
|
});
|
|
12516
|
-
|
|
12517
|
-
it('disconnects it first if the data channel url has changed', async () => {
|
|
12886
|
+
it('disconnects if the data channel url has changed', async () => {
|
|
12518
12887
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12519
12888
|
webex.internal.llm.isConnected.returns(true);
|
|
12520
12889
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12521
|
-
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
12522
12890
|
|
|
12523
|
-
meeting.locusInfo = {
|
|
12891
|
+
meeting.locusInfo = {
|
|
12892
|
+
url: 'a url',
|
|
12893
|
+
info: {datachannelUrl: 'a different datachannel url'},
|
|
12894
|
+
self: {},
|
|
12895
|
+
};
|
|
12524
12896
|
|
|
12525
12897
|
const result = await meeting.updateLLMConnection();
|
|
12526
12898
|
|
|
12527
|
-
assert.
|
|
12899
|
+
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
12528
12900
|
code: 3050,
|
|
12529
12901
|
reason: 'done (permanent)',
|
|
12530
12902
|
});
|
|
12531
|
-
|
|
12903
|
+
|
|
12904
|
+
assert.calledWithExactly(
|
|
12532
12905
|
webex.internal.llm.registerAndConnect,
|
|
12533
12906
|
'a url',
|
|
12534
|
-
'a different datachannel url'
|
|
12907
|
+
'a different datachannel url',
|
|
12908
|
+
undefined
|
|
12535
12909
|
);
|
|
12910
|
+
|
|
12536
12911
|
assert.equal(result, 'something');
|
|
12912
|
+
|
|
12537
12913
|
assert.calledWithExactly(
|
|
12538
12914
|
meeting.webex.internal.llm.off,
|
|
12539
12915
|
'event:relay.event',
|
|
@@ -12544,6 +12920,7 @@ describe('plugin-meetings', () => {
|
|
|
12544
12920
|
'event:locus.state_message',
|
|
12545
12921
|
meeting.processLocusLLMEvent
|
|
12546
12922
|
);
|
|
12923
|
+
|
|
12547
12924
|
assert.calledWithExactly(
|
|
12548
12925
|
meeting.webex.internal.llm.on,
|
|
12549
12926
|
'event:relay.event',
|
|
@@ -12554,8 +12931,10 @@ describe('plugin-meetings', () => {
|
|
|
12554
12931
|
'event:locus.state_message',
|
|
12555
12932
|
meeting.processLocusLLMEvent
|
|
12556
12933
|
);
|
|
12934
|
+
assert.isFalse(
|
|
12935
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
12936
|
+
);
|
|
12557
12937
|
});
|
|
12558
|
-
|
|
12559
12938
|
it('disconnects when the state is not JOINED', async () => {
|
|
12560
12939
|
meeting.joinedWith = {state: 'any other state'};
|
|
12561
12940
|
webex.internal.llm.isConnected.returns(true);
|
|
@@ -12565,9 +12944,38 @@ describe('plugin-meetings', () => {
|
|
|
12565
12944
|
|
|
12566
12945
|
const result = await meeting.updateLLMConnection();
|
|
12567
12946
|
|
|
12568
|
-
assert.calledWith(webex.internal.llm.disconnectLLM,
|
|
12947
|
+
assert.calledWith(webex.internal.llm.disconnectLLM, {
|
|
12948
|
+
code: 3050,
|
|
12949
|
+
reason: 'done (permanent)',
|
|
12950
|
+
});
|
|
12569
12951
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12570
12952
|
assert.equal(result, undefined);
|
|
12953
|
+
assert.isFalse(
|
|
12954
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
12955
|
+
);
|
|
12956
|
+
});
|
|
12957
|
+
it('rethrows disconnect errors during reconnect cleanup after removing relay listeners and timer', async () => {
|
|
12958
|
+
const disconnectError = new Error('disconnect failed');
|
|
12959
|
+
|
|
12960
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
12961
|
+
webex.internal.llm.isConnected.returns(true);
|
|
12962
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12963
|
+
webex.internal.llm.disconnectLLM.rejects(disconnectError);
|
|
12964
|
+
|
|
12965
|
+
meeting.locusInfo = {
|
|
12966
|
+
url: 'a different url',
|
|
12967
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12968
|
+
self: {},
|
|
12969
|
+
};
|
|
12970
|
+
|
|
12971
|
+
try {
|
|
12972
|
+
await meeting.updateLLMConnection();
|
|
12973
|
+
assert.fail('Expected updateLLMConnection to reject when disconnectLLM fails');
|
|
12974
|
+
} catch (error) {
|
|
12975
|
+
assert.equal(error, disconnectError);
|
|
12976
|
+
}
|
|
12977
|
+
|
|
12978
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12571
12979
|
assert.calledWithExactly(
|
|
12572
12980
|
meeting.webex.internal.llm.off,
|
|
12573
12981
|
'event:relay.event',
|
|
@@ -12578,22 +12986,149 @@ describe('plugin-meetings', () => {
|
|
|
12578
12986
|
'event:locus.state_message',
|
|
12579
12987
|
meeting.processLocusLLMEvent
|
|
12580
12988
|
);
|
|
12989
|
+
assert.isFalse(
|
|
12990
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
12991
|
+
);
|
|
12992
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
12581
12993
|
});
|
|
12582
|
-
|
|
12583
|
-
it('connect ps data channel if ps started in webinar', async () => {
|
|
12994
|
+
it('still need connect main session data channel when PS started', async () => {
|
|
12584
12995
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12585
12996
|
meeting.locusInfo = {
|
|
12586
12997
|
url: 'a url',
|
|
12587
12998
|
info: {
|
|
12588
12999
|
datachannelUrl: 'a datachannel url',
|
|
12589
|
-
practiceSessionDatachannelUrl: '
|
|
13000
|
+
practiceSessionDatachannelUrl: 'ps-url',
|
|
12590
13001
|
},
|
|
12591
13002
|
};
|
|
12592
|
-
meeting.webinar.isJoinPracticeSessionDataChannel
|
|
13003
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
13004
|
+
|
|
12593
13005
|
await meeting.updateLLMConnection();
|
|
12594
13006
|
|
|
12595
|
-
assert.
|
|
12596
|
-
|
|
13007
|
+
assert.calledWithExactly(
|
|
13008
|
+
webex.internal.llm.registerAndConnect,
|
|
13009
|
+
'a url',
|
|
13010
|
+
'a datachannel url',
|
|
13011
|
+
undefined
|
|
13012
|
+
);
|
|
13013
|
+
});
|
|
13014
|
+
it('passes dataChannelToken to registerAndConnect', async () => {
|
|
13015
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13016
|
+
meeting.locusInfo = {
|
|
13017
|
+
url: 'a url',
|
|
13018
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13019
|
+
self: {datachannelToken: 'token-123'},
|
|
13020
|
+
};
|
|
13021
|
+
|
|
13022
|
+
webex.internal.llm.getDatachannelToken.returns(undefined);
|
|
13023
|
+
|
|
13024
|
+
await meeting.updateLLMConnection();
|
|
13025
|
+
|
|
13026
|
+
assert.calledWithExactly(
|
|
13027
|
+
webex.internal.llm.registerAndConnect,
|
|
13028
|
+
'a url',
|
|
13029
|
+
'a datachannel url',
|
|
13030
|
+
'token-123'
|
|
13031
|
+
);
|
|
13032
|
+
assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'llm-default-session');
|
|
13033
|
+
});
|
|
13034
|
+
it('prefers refreshed token over locus self token', async () => {
|
|
13035
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13036
|
+
meeting.locusInfo = {
|
|
13037
|
+
url: 'a url',
|
|
13038
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13039
|
+
self: {datachannelToken: 'locus-token'},
|
|
13040
|
+
};
|
|
13041
|
+
|
|
13042
|
+
webex.internal.llm.getDatachannelToken.withArgs('llm-default-session').returns('refreshed-token');
|
|
13043
|
+
|
|
13044
|
+
await meeting.updateLLMConnection();
|
|
13045
|
+
|
|
13046
|
+
assert.calledWithExactly(
|
|
13047
|
+
webex.internal.llm.registerAndConnect,
|
|
13048
|
+
'a url',
|
|
13049
|
+
'a datachannel url',
|
|
13050
|
+
'refreshed-token'
|
|
13051
|
+
);
|
|
13052
|
+
|
|
13053
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13054
|
+
});
|
|
13055
|
+
|
|
13056
|
+
it('does not pass token when data channel with jwt token is disabled', async () => {
|
|
13057
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13058
|
+
meeting.locusInfo = {
|
|
13059
|
+
url: 'a url',
|
|
13060
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13061
|
+
self: {datachannelToken: 'token-123'},
|
|
13062
|
+
};
|
|
13063
|
+
|
|
13064
|
+
webex.internal.llm.getDatachannelToken.returns(undefined);
|
|
13065
|
+
webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
|
|
13066
|
+
|
|
13067
|
+
await meeting.updateLLMConnection();
|
|
13068
|
+
|
|
13069
|
+
assert.calledWithExactly(
|
|
13070
|
+
webex.internal.llm.registerAndConnect,
|
|
13071
|
+
'a url',
|
|
13072
|
+
'a datachannel url',
|
|
13073
|
+
'token-123'
|
|
13074
|
+
);
|
|
13075
|
+
assert.calledWithExactly(webex.internal.llm.setDatachannelToken, 'token-123', 'llm-default-session');
|
|
13076
|
+
});
|
|
13077
|
+
|
|
13078
|
+
describe('#clearMeetingData', () => {
|
|
13079
|
+
beforeEach(() => {
|
|
13080
|
+
webex.internal.llm.isConnected = sinon.stub().returns(true);
|
|
13081
|
+
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
13082
|
+
webex.internal.llm.off = sinon.stub();
|
|
13083
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
13084
|
+
meeting.clearLLMHealthCheckTimer = sinon.stub();
|
|
13085
|
+
meeting.stopTranscription = sinon.stub();
|
|
13086
|
+
meeting.transcription = {};
|
|
13087
|
+
meeting.shareStatus = 'no-share';
|
|
13088
|
+
});
|
|
13089
|
+
|
|
13090
|
+
it('disconnects llm and removes online and relay listeners during meeting data cleanup', async () => {
|
|
13091
|
+
await meeting.clearMeetingData();
|
|
13092
|
+
|
|
13093
|
+
assert.calledOnceWithExactly(webex.internal.llm.disconnectLLM, {
|
|
13094
|
+
code: 3050,
|
|
13095
|
+
reason: 'done (permanent)',
|
|
13096
|
+
});
|
|
13097
|
+
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
13098
|
+
assert.calledWithExactly(
|
|
13099
|
+
webex.internal.llm.off,
|
|
13100
|
+
'event:relay.event',
|
|
13101
|
+
meeting.processRelayEvent
|
|
13102
|
+
);
|
|
13103
|
+
assert.calledWithExactly(
|
|
13104
|
+
webex.internal.llm.off,
|
|
13105
|
+
'event:locus.state_message',
|
|
13106
|
+
meeting.processLocusLLMEvent
|
|
13107
|
+
);
|
|
13108
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13109
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13110
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13111
|
+
});
|
|
13112
|
+
it('continues cleanup when disconnectLLM fails during meeting data cleanup', async () => {
|
|
13113
|
+
webex.internal.llm.disconnectLLM.rejects(new Error('disconnect failed'));
|
|
13114
|
+
|
|
13115
|
+
await meeting.clearMeetingData();
|
|
13116
|
+
|
|
13117
|
+
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
13118
|
+
assert.calledWithExactly(
|
|
13119
|
+
webex.internal.llm.off,
|
|
13120
|
+
'event:relay.event',
|
|
13121
|
+
meeting.processRelayEvent
|
|
13122
|
+
);
|
|
13123
|
+
assert.calledWithExactly(
|
|
13124
|
+
webex.internal.llm.off,
|
|
13125
|
+
'event:locus.state_message',
|
|
13126
|
+
meeting.processLocusLLMEvent
|
|
13127
|
+
);
|
|
13128
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13129
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13130
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13131
|
+
});
|
|
12597
13132
|
});
|
|
12598
13133
|
});
|
|
12599
13134
|
|
|
@@ -12604,6 +13139,7 @@ describe('plugin-meetings', () => {
|
|
|
12604
13139
|
|
|
12605
13140
|
it('should read the locus object, set on the meeting and return null', () => {
|
|
12606
13141
|
const dataSets = {someFakeStuff: 'dataSet'};
|
|
13142
|
+
const metadata = {some: 'metadata'};
|
|
12607
13143
|
|
|
12608
13144
|
meeting.setLocus({
|
|
12609
13145
|
mediaConnections: [test1],
|
|
@@ -12613,12 +13149,14 @@ describe('plugin-meetings', () => {
|
|
|
12613
13149
|
mediaId: uuid3,
|
|
12614
13150
|
locus: {host: {id: uuid4}},
|
|
12615
13151
|
dataSets,
|
|
13152
|
+
metadata,
|
|
12616
13153
|
});
|
|
12617
13154
|
assert.calledOnce(meeting.locusInfo.initialSetup);
|
|
12618
13155
|
assert.calledWith(meeting.locusInfo.initialSetup, {
|
|
12619
13156
|
trigger: 'join-response',
|
|
12620
13157
|
locus: {host: {id: uuid4}},
|
|
12621
13158
|
dataSets,
|
|
13159
|
+
metadata,
|
|
12622
13160
|
});
|
|
12623
13161
|
assert.equal(meeting.mediaConnections, test1);
|
|
12624
13162
|
assert.equal(meeting.locusUrl, url1);
|
|
@@ -14160,6 +14698,69 @@ describe('plugin-meetings', () => {
|
|
|
14160
14698
|
assert.calledOnce(meeting.meetingRequest.keepAlive);
|
|
14161
14699
|
});
|
|
14162
14700
|
});
|
|
14701
|
+
describe('#refreshDataChannelToken()', () => {
|
|
14702
|
+
let meeting;
|
|
14703
|
+
|
|
14704
|
+
beforeEach(() => {
|
|
14705
|
+
meeting = Object.create(Meeting.prototype);
|
|
14706
|
+
meeting.locusUrl = 'https://locus.example.com';
|
|
14707
|
+
meeting.meetingRequest = {
|
|
14708
|
+
fetchDatachannelToken: sinon.stub().resolves({
|
|
14709
|
+
body: {datachannelToken: 'mock-token'},
|
|
14710
|
+
}),
|
|
14711
|
+
};
|
|
14712
|
+
meeting.members = {
|
|
14713
|
+
selfId: 'self-123',
|
|
14714
|
+
};
|
|
14715
|
+
meeting.webinar = {
|
|
14716
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
|
|
14717
|
+
};
|
|
14718
|
+
});
|
|
14719
|
+
|
|
14720
|
+
it('calls fetchDatachannelToken with correct parameters', async () => {
|
|
14721
|
+
await meeting.refreshDataChannelToken();
|
|
14722
|
+
|
|
14723
|
+
sinon.assert.calledOnce(meeting.meetingRequest.fetchDatachannelToken);
|
|
14724
|
+
|
|
14725
|
+
sinon.assert.calledWith(meeting.meetingRequest.fetchDatachannelToken, {
|
|
14726
|
+
locusUrl: 'https://locus.example.com',
|
|
14727
|
+
requestingParticipantId: 'self-123',
|
|
14728
|
+
isPracticeSession: true,
|
|
14729
|
+
});
|
|
14730
|
+
});
|
|
14731
|
+
|
|
14732
|
+
it('returns the correct structured result', async () => {
|
|
14733
|
+
const result = await meeting.refreshDataChannelToken();
|
|
14734
|
+
|
|
14735
|
+
expect(result).to.deep.equal({
|
|
14736
|
+
body: {
|
|
14737
|
+
datachannelToken: 'mock-token',
|
|
14738
|
+
dataChannelTokenType: 'llm-practice-session',
|
|
14739
|
+
},
|
|
14740
|
+
});
|
|
14741
|
+
});
|
|
14742
|
+
});
|
|
14743
|
+
describe('#getDataChannelTokenType', () => {
|
|
14744
|
+
it('returns PracticeSession when webinar is in practice session mode', () => {
|
|
14745
|
+
meeting.webinar = {
|
|
14746
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
|
|
14747
|
+
};
|
|
14748
|
+
|
|
14749
|
+
const result = meeting.getDataChannelTokenType();
|
|
14750
|
+
|
|
14751
|
+
expect(result).to.equal('llm-practice-session');
|
|
14752
|
+
});
|
|
14753
|
+
|
|
14754
|
+
it('returns Default when not in practice session mode', () => {
|
|
14755
|
+
meeting.webinar = {
|
|
14756
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(false),
|
|
14757
|
+
};
|
|
14758
|
+
|
|
14759
|
+
const result = meeting.getDataChannelTokenType();
|
|
14760
|
+
|
|
14761
|
+
expect(result).to.equal('llm-default-session');
|
|
14762
|
+
});
|
|
14763
|
+
});
|
|
14163
14764
|
describe('#stopKeepAlive', () => {
|
|
14164
14765
|
let clock;
|
|
14165
14766
|
const defaultKeepAliveUrl = 'keep.alive.url';
|