@webex/plugin-meetings 3.11.0-webex-services-ready.1 → 3.12.0-mobius-socket.1
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 +868 -419
- 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 +1293 -929
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +50 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +133 -3
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +117 -48
- package/dist/meetings/index.js.map +1 -1
- package/dist/member/index.js +10 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/util.js +10 -0
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +6 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +9 -60
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +11 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +116 -2
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/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 +72 -7
- package/dist/types/meeting/request.d.ts +16 -1
- package/dist/types/meeting/request.type.d.ts +5 -0
- package/dist/types/meeting/util.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +4 -2
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/util.d.ts +5 -0
- package/dist/types/metrics/constants.d.ts +5 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +0 -23
- package/dist/types/multistream/sendSlotManager.d.ts +23 -1
- package/dist/types/reactions/reactions.type.d.ts +1 -0
- package/dist/types/webinar/utils.d.ts +6 -0
- package/dist/webinar/index.js +438 -163
- package/dist/webinar/index.js.map +1 -1
- package/dist/webinar/utils.js +25 -0
- package/dist/webinar/utils.js.map +1 -0
- package/package.json +24 -23
- package/src/aiEnableRequest/README.md +84 -0
- package/src/aiEnableRequest/index.ts +170 -0
- package/src/aiEnableRequest/utils.ts +25 -0
- package/src/annotation/index.ts +27 -7
- package/src/config.ts +4 -0
- package/src/constants.ts +29 -1
- package/src/hashTree/constants.ts +1 -0
- package/src/hashTree/hashTree.ts +17 -0
- package/src/hashTree/hashTreeParser.ts +761 -260
- 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 +372 -86
- package/src/meeting/request.ts +42 -0
- package/src/meeting/request.type.ts +6 -0
- package/src/meeting/util.ts +160 -2
- package/src/meetings/index.ts +157 -44
- package/src/member/index.ts +10 -0
- package/src/member/util.ts +12 -0
- package/src/metrics/constants.ts +6 -0
- package/src/multistream/mediaRequestManager.ts +4 -54
- package/src/multistream/remoteMediaManager.ts +13 -0
- package/src/multistream/sendSlotManager.ts +97 -3
- package/src/reactions/reactions.type.ts +1 -0
- package/src/reconnection-manager/index.ts +0 -1
- package/src/webinar/index.ts +265 -6
- package/src/webinar/utils.ts +16 -0
- package/test/unit/spec/aiEnableRequest/index.ts +981 -0
- package/test/unit/spec/aiEnableRequest/utils.ts +130 -0
- package/test/unit/spec/annotation/index.ts +69 -7
- package/test/unit/spec/hashTree/hashTree.ts +66 -0
- package/test/unit/spec/hashTree/hashTreeParser.ts +2321 -175
- 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 -121
- package/test/unit/spec/meeting/request.js +70 -0
- package/test/unit/spec/meeting/utils.js +438 -26
- package/test/unit/spec/meetings/index.js +653 -32
- package/test/unit/spec/member/index.js +28 -4
- package/test/unit/spec/member/util.js +65 -27
- package/test/unit/spec/multistream/mediaRequestManager.ts +2 -85
- package/test/unit/spec/multistream/remoteMediaManager.ts +30 -0
- package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
- package/test/unit/spec/reconnection-manager/index.js +4 -8
- package/test/unit/spec/webinar/index.ts +534 -37
- package/test/unit/spec/webinar/utils.ts +39 -0
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
import {
|
|
39
39
|
ConnectionState,
|
|
40
40
|
MediaConnectionEventNames,
|
|
41
|
+
MediaCodecMimeType,
|
|
41
42
|
StatsAnalyzerEventNames,
|
|
42
43
|
StatsMonitorEventNames,
|
|
43
44
|
Errors,
|
|
@@ -82,6 +83,7 @@ import Mercury from '@webex/internal-plugin-mercury';
|
|
|
82
83
|
import Breakouts from '@webex/plugin-meetings/src/breakouts';
|
|
83
84
|
import SimultaneousInterpretation from '@webex/plugin-meetings/src/interpretation';
|
|
84
85
|
import Webinar from '@webex/plugin-meetings/src/webinar';
|
|
86
|
+
import AIEnableRequest from '@webex/plugin-meetings/src/aiEnableRequest';
|
|
85
87
|
import {REACTION_RELAY_TYPES} from '../../../../src/reactions/constants';
|
|
86
88
|
import locus from '../fixture/locus';
|
|
87
89
|
import {
|
|
@@ -123,7 +125,6 @@ import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
|
|
|
123
125
|
import {createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
|
|
124
126
|
import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
|
|
125
127
|
import {EventEmitter} from 'stream';
|
|
126
|
-
|
|
127
128
|
describe('plugin-meetings', () => {
|
|
128
129
|
const logger = {
|
|
129
130
|
info: () => {},
|
|
@@ -265,7 +266,9 @@ describe('plugin-meetings', () => {
|
|
|
265
266
|
stopReachability: sinon.stub(),
|
|
266
267
|
isSubnetReachable: sinon.stub().returns(true),
|
|
267
268
|
};
|
|
269
|
+
webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
|
|
268
270
|
webex.internal.llm.on = sinon.stub();
|
|
271
|
+
webex.internal.voicea.announce = sinon.stub();
|
|
269
272
|
webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
|
|
270
273
|
{},
|
|
271
274
|
{parent: webex}
|
|
@@ -376,6 +379,7 @@ describe('plugin-meetings', () => {
|
|
|
376
379
|
assert.instanceOf(meeting.breakouts, Breakouts);
|
|
377
380
|
assert.instanceOf(meeting.simultaneousInterpretation, SimultaneousInterpretation);
|
|
378
381
|
assert.instanceOf(meeting.webinar, Webinar);
|
|
382
|
+
assert.instanceOf(meeting.aiEnableRequest, AIEnableRequest);
|
|
379
383
|
});
|
|
380
384
|
|
|
381
385
|
it('should call the callback with the meeting that has id already set', () => {
|
|
@@ -738,7 +742,9 @@ describe('plugin-meetings', () => {
|
|
|
738
742
|
let supportsRTCPeerConnectionStub;
|
|
739
743
|
|
|
740
744
|
beforeEach(() => {
|
|
741
|
-
supportsRTCPeerConnectionStub = sinon
|
|
745
|
+
supportsRTCPeerConnectionStub = sinon
|
|
746
|
+
.stub(WebCapabilities, 'supportsRTCPeerConnection')
|
|
747
|
+
.returns(CapabilityState.CAPABLE);
|
|
742
748
|
|
|
743
749
|
meeting.join = sinon.stub().callsFake((joinOptions) => {
|
|
744
750
|
meeting.isMultistream = joinOptions.enableMultistream;
|
|
@@ -1011,33 +1017,53 @@ describe('plugin-meetings', () => {
|
|
|
1011
1017
|
);
|
|
1012
1018
|
});
|
|
1013
1019
|
|
|
1014
|
-
it('should call leave() if addMediaInternal() fails ', async () => {
|
|
1020
|
+
it('should call leave() if addMediaInternal() fails with a browser media error (TypeError)', async () => {
|
|
1015
1021
|
const addMediaError = new Error('fake addMedia error');
|
|
1016
|
-
addMediaError.name = 'TypeError';
|
|
1022
|
+
addMediaError.name = 'TypeError'; // This makes it a browser media error
|
|
1017
1023
|
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
body: {
|
|
1021
|
-
errorCode: 2729,
|
|
1022
|
-
message: 'fake addMedia error',
|
|
1023
|
-
name: 'TypeError'
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
};
|
|
1027
|
-
meeting.addMediaInternal.rejects(addMediaError);
|
|
1028
|
-
sinon.stub(meeting, 'leave').resolves();
|
|
1024
|
+
const leaveStub = sinon.stub(meeting, 'leave').resolves();
|
|
1025
|
+
meeting.addMediaInternal = sinon.stub().rejects(addMediaError);
|
|
1029
1026
|
|
|
1030
|
-
|
|
1027
|
+
// When a browser media error occurs, it gets transformed into a special structure
|
|
1028
|
+
const rejectedError = await assert.isRejected(
|
|
1031
1029
|
meeting.joinWithMedia({
|
|
1032
1030
|
joinOptions,
|
|
1033
1031
|
mediaOptions,
|
|
1034
|
-
})
|
|
1035
|
-
rejectError
|
|
1032
|
+
})
|
|
1036
1033
|
);
|
|
1037
1034
|
|
|
1035
|
+
// Verify the error was transformed with errorCode 2729
|
|
1036
|
+
assert.equal(rejectedError.error.body.errorCode, 2729);
|
|
1037
|
+
assert.equal(rejectedError.error.body.message, 'fake addMedia error');
|
|
1038
|
+
assert.equal(rejectedError.error.body.name, 'TypeError');
|
|
1039
|
+
|
|
1038
1040
|
assert.calledOnce(meeting.join);
|
|
1039
1041
|
assert.calledOnce(meeting.addMediaInternal);
|
|
1042
|
+
assert.calledOnce(leaveStub);
|
|
1043
|
+
assert.calledOnceWithExactly(leaveStub, {
|
|
1044
|
+
resourceId: undefined,
|
|
1045
|
+
reason: 'joinWithMedia failure',
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
// Browser media errors don't retry, so behavioral metric is sent only once
|
|
1049
|
+
// NOTE: The error gets transformed, so the metric receives undefined for message/stack/name
|
|
1050
|
+
// because they're now nested in error.body instead of at the top level
|
|
1040
1051
|
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
1052
|
+
assert.calledWith(
|
|
1053
|
+
Metrics.sendBehavioralMetric,
|
|
1054
|
+
BEHAVIORAL_METRICS.JOIN_WITH_MEDIA_FAILURE,
|
|
1055
|
+
{
|
|
1056
|
+
correlation_id: meeting.correlationId,
|
|
1057
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1058
|
+
reason: undefined, // transformed error doesn't have .message at top level
|
|
1059
|
+
stack: undefined, // transformed error doesn't have .stack at top level
|
|
1060
|
+
leaveErrorReason: undefined,
|
|
1061
|
+
isRetry: false,
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
type: undefined, // transformed error doesn't have .name at top level
|
|
1065
|
+
}
|
|
1066
|
+
);
|
|
1041
1067
|
});
|
|
1042
1068
|
|
|
1043
1069
|
it('should not call leave() if addMediaInternal() fails the first time and succeeds the second time and should only call join() once', async () => {
|
|
@@ -1249,8 +1275,14 @@ describe('plugin-meetings', () => {
|
|
|
1249
1275
|
});
|
|
1250
1276
|
|
|
1251
1277
|
[
|
|
1252
|
-
{
|
|
1253
|
-
|
|
1278
|
+
{
|
|
1279
|
+
errorName: 'SdpOfferCreationError',
|
|
1280
|
+
description: 'if we fail to create the offer on first attempt',
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
errorName: 'WebrtcApiNotAvailableError',
|
|
1284
|
+
description: 'if RTCPeerConnection is not available',
|
|
1285
|
+
},
|
|
1254
1286
|
].forEach(({errorName, description}) => {
|
|
1255
1287
|
it(`should not attempt a retry ${description}`, async () => {
|
|
1256
1288
|
const addMediaError = new Error('fake addMedia error');
|
|
@@ -1290,7 +1322,7 @@ describe('plugin-meetings', () => {
|
|
|
1290
1322
|
resourceId: undefined,
|
|
1291
1323
|
reason: 'joinWithMedia failure',
|
|
1292
1324
|
});
|
|
1293
|
-
})
|
|
1325
|
+
});
|
|
1294
1326
|
});
|
|
1295
1327
|
|
|
1296
1328
|
it('should ignore sendVideo/receiveVideo when videoEnabled is false', async () => {
|
|
@@ -1521,6 +1553,22 @@ describe('plugin-meetings', () => {
|
|
|
1521
1553
|
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
1522
1554
|
);
|
|
1523
1555
|
});
|
|
1556
|
+
|
|
1557
|
+
it('should stop listening to voicea events even when transcription is undefined', () => {
|
|
1558
|
+
meeting.transcription = undefined;
|
|
1559
|
+
meeting.stopTranscription();
|
|
1560
|
+
assert.equal(webex.internal.voicea.off.callCount, 4);
|
|
1561
|
+
assert.equal(meeting.areVoiceaEventsSetup, false);
|
|
1562
|
+
assert.calledWith(
|
|
1563
|
+
TriggerProxy.trigger,
|
|
1564
|
+
sinon.match.instanceOf(Meeting),
|
|
1565
|
+
{
|
|
1566
|
+
file: 'meeting/index',
|
|
1567
|
+
function: 'triggerStopReceivingTranscriptionEvent',
|
|
1568
|
+
},
|
|
1569
|
+
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
1570
|
+
);
|
|
1571
|
+
});
|
|
1524
1572
|
});
|
|
1525
1573
|
|
|
1526
1574
|
describe('#setCaptionLanguage', () => {
|
|
@@ -1882,16 +1930,64 @@ describe('plugin-meetings', () => {
|
|
|
1882
1930
|
fakeProcessedReaction
|
|
1883
1931
|
);
|
|
1884
1932
|
});
|
|
1933
|
+
|
|
1934
|
+
it('should process if participantId does not exist in membersCollection but has displayName in Webinar', () => {
|
|
1935
|
+
LoggerProxy.logger.warn = sinon.stub();
|
|
1936
|
+
meeting.isReactionsSupported = sinon.stub().returns(true);
|
|
1937
|
+
meeting.config.receiveReactions = true;
|
|
1938
|
+
meeting.locusInfo.info = {isWebinar: true};
|
|
1939
|
+
const fakeSendersName = 'Fake reactors name';
|
|
1940
|
+
const fakeReactionPayload = {
|
|
1941
|
+
type: 'fake_type',
|
|
1942
|
+
codepoints: 'fake_codepoints',
|
|
1943
|
+
shortcodes: 'fake_shortcodes',
|
|
1944
|
+
tone: {
|
|
1945
|
+
type: 'fake_tone_type',
|
|
1946
|
+
codepoints: 'fake_tone_codepoints',
|
|
1947
|
+
shortcodes: 'fake_tone_shortcodes',
|
|
1948
|
+
},
|
|
1949
|
+
};
|
|
1950
|
+
const fakeSenderPayload = {
|
|
1951
|
+
displayName: 'Fake reactors name',
|
|
1952
|
+
participantId: 'fake_participant_id',
|
|
1953
|
+
};
|
|
1954
|
+
const fakeProcessedReaction = {
|
|
1955
|
+
reaction: fakeReactionPayload,
|
|
1956
|
+
sender: {
|
|
1957
|
+
id: fakeSenderPayload.participantId,
|
|
1958
|
+
name: fakeSendersName,
|
|
1959
|
+
},
|
|
1960
|
+
};
|
|
1961
|
+
const fakeRelayEvent = {
|
|
1962
|
+
data: {
|
|
1963
|
+
relayType: REACTION_RELAY_TYPES.REACTION,
|
|
1964
|
+
reaction: fakeReactionPayload,
|
|
1965
|
+
sender: fakeSenderPayload,
|
|
1966
|
+
},
|
|
1967
|
+
};
|
|
1968
|
+
meeting.processRelayEvent(fakeRelayEvent);
|
|
1969
|
+
assert.calledWith(
|
|
1970
|
+
TriggerProxy.trigger,
|
|
1971
|
+
sinon.match.instanceOf(Meeting),
|
|
1972
|
+
{
|
|
1973
|
+
file: 'meeting/index',
|
|
1974
|
+
function: 'join',
|
|
1975
|
+
},
|
|
1976
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
1977
|
+
fakeProcessedReaction
|
|
1978
|
+
);
|
|
1979
|
+
});
|
|
1885
1980
|
});
|
|
1886
1981
|
|
|
1887
1982
|
describe('#handleLLMOnline', () => {
|
|
1888
1983
|
beforeEach(() => {
|
|
1889
1984
|
webex.internal.llm.off = sinon.stub();
|
|
1985
|
+
webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
|
|
1986
|
+
webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
|
|
1890
1987
|
});
|
|
1891
1988
|
|
|
1892
|
-
it('
|
|
1989
|
+
it('emits transcription connected events', () => {
|
|
1893
1990
|
meeting.handleLLMOnline();
|
|
1894
|
-
assert.calledOnceWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
1895
1991
|
assert.calledWith(
|
|
1896
1992
|
TriggerProxy.trigger,
|
|
1897
1993
|
sinon.match.instanceOf(Meeting),
|
|
@@ -1902,6 +1998,24 @@ describe('plugin-meetings', () => {
|
|
|
1902
1998
|
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
|
|
1903
1999
|
);
|
|
1904
2000
|
});
|
|
2001
|
+
|
|
2002
|
+
it('restores transcription subscription when caption intent is enabled', () => {
|
|
2003
|
+
webex.internal.voicea.getIsCaptionBoxOn.returns(true);
|
|
2004
|
+
|
|
2005
|
+
meeting.handleLLMOnline();
|
|
2006
|
+
|
|
2007
|
+
assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, {
|
|
2008
|
+
subscribe: ['transcription'],
|
|
2009
|
+
});
|
|
2010
|
+
});
|
|
2011
|
+
|
|
2012
|
+
it('does not restore transcription subscription when caption intent is disabled', () => {
|
|
2013
|
+
webex.internal.voicea.getIsCaptionBoxOn.returns(false);
|
|
2014
|
+
|
|
2015
|
+
meeting.handleLLMOnline();
|
|
2016
|
+
|
|
2017
|
+
assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
|
|
2018
|
+
});
|
|
1905
2019
|
});
|
|
1906
2020
|
|
|
1907
2021
|
describe('#join', () => {
|
|
@@ -1921,6 +2035,7 @@ describe('plugin-meetings', () => {
|
|
|
1921
2035
|
it('should have #join', () => {
|
|
1922
2036
|
assert.exists(meeting.join);
|
|
1923
2037
|
});
|
|
2038
|
+
|
|
1924
2039
|
beforeEach(() => {
|
|
1925
2040
|
setCorrelationIdSpy = sinon.spy(meeting, 'setCorrelationId');
|
|
1926
2041
|
meeting.setLocus = sinon.stub().returns(true);
|
|
@@ -2074,7 +2189,6 @@ describe('plugin-meetings', () => {
|
|
|
2074
2189
|
await meeting.join().catch(() => {
|
|
2075
2190
|
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
2076
2191
|
|
|
2077
|
-
// Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
|
|
2078
2192
|
assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
|
|
2079
2193
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
2080
2194
|
name: 'client.call.initiated',
|
|
@@ -2106,6 +2220,7 @@ describe('plugin-meetings', () => {
|
|
|
2106
2220
|
});
|
|
2107
2221
|
});
|
|
2108
2222
|
});
|
|
2223
|
+
|
|
2109
2224
|
describe('lmm, transcription & permissionTokenRefresh decoupling', () => {
|
|
2110
2225
|
beforeEach(() => {
|
|
2111
2226
|
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
|
|
@@ -2176,7 +2291,6 @@ describe('plugin-meetings', () => {
|
|
|
2176
2291
|
const locusInfoParseStub = sinon.stub(meeting.locusInfo, 'parse');
|
|
2177
2292
|
sinon.stub(meeting, 'isJoined').returns(true);
|
|
2178
2293
|
|
|
2179
|
-
// Set up llm.on stub to capture the registered listener when updateLLMConnection is called
|
|
2180
2294
|
let locusLLMEventListener;
|
|
2181
2295
|
meeting.webex.internal.llm.on = sinon.stub().callsFake((eventName, callback) => {
|
|
2182
2296
|
if (eventName === 'event:locus.state_message') {
|
|
@@ -2185,16 +2299,12 @@ describe('plugin-meetings', () => {
|
|
|
2185
2299
|
});
|
|
2186
2300
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
2187
2301
|
|
|
2188
|
-
// we need the real meeting.updateLLMConnection not the mock
|
|
2189
2302
|
meeting.updateLLMConnection.restore();
|
|
2190
2303
|
|
|
2191
|
-
// Call updateLLMConnection to register the listener
|
|
2192
2304
|
await meeting.updateLLMConnection();
|
|
2193
2305
|
|
|
2194
|
-
// Verify the listener was registered and we captured it
|
|
2195
2306
|
assert.isDefined(locusLLMEventListener, 'LLM event listener should be registered');
|
|
2196
2307
|
|
|
2197
|
-
// Now trigger the event
|
|
2198
2308
|
const eventData = {
|
|
2199
2309
|
eventType: 'locus.state_message',
|
|
2200
2310
|
stateElementsMessage: {
|
|
@@ -2214,13 +2324,10 @@ describe('plugin-meetings', () => {
|
|
|
2214
2324
|
sinon.stub(meeting.webex.internal.llm, 'hasEverConnected').value(true);
|
|
2215
2325
|
sinon.stub(meeting.webex.internal.llm, 'registerAndConnect').resolves({});
|
|
2216
2326
|
|
|
2217
|
-
// Restore the real updateLLMConnection
|
|
2218
2327
|
meeting.updateLLMConnection.restore();
|
|
2219
2328
|
|
|
2220
|
-
// Call updateLLMConnection to start the timer
|
|
2221
2329
|
await meeting.updateLLMConnection();
|
|
2222
2330
|
|
|
2223
|
-
// Fast forward time by 3 minutes
|
|
2224
2331
|
fakeClock.tick(3 * 60 * 1000);
|
|
2225
2332
|
|
|
2226
2333
|
assert.calledWith(
|
|
@@ -2245,18 +2352,14 @@ describe('plugin-meetings', () => {
|
|
|
2245
2352
|
.stub(meeting.webex.internal.llm, 'getDatachannelUrl')
|
|
2246
2353
|
.returns('https://datachannel1.example.com');
|
|
2247
2354
|
|
|
2248
|
-
// Restore the real updateLLMConnection
|
|
2249
2355
|
meeting.updateLLMConnection.restore();
|
|
2250
2356
|
|
|
2251
|
-
// First, connect LLM and start the timer
|
|
2252
2357
|
isJoinedStub.returns(true);
|
|
2253
2358
|
meeting.webex.internal.llm.isConnected.returns(false);
|
|
2254
2359
|
await meeting.updateLLMConnection();
|
|
2255
2360
|
|
|
2256
|
-
// Verify timer was started
|
|
2257
2361
|
assert.exists(meeting.llmHealthCheckTimer);
|
|
2258
2362
|
|
|
2259
|
-
// Now simulate that we're no longer joined
|
|
2260
2363
|
isJoinedStub.returns(false);
|
|
2261
2364
|
meeting.webex.internal.llm.isConnected.returns(true);
|
|
2262
2365
|
|
|
@@ -2264,10 +2367,8 @@ describe('plugin-meetings', () => {
|
|
|
2264
2367
|
|
|
2265
2368
|
assert.calledOnce(meeting.webex.internal.llm.disconnectLLM);
|
|
2266
2369
|
|
|
2267
|
-
// Verify the timer was cleared (should be undefined)
|
|
2268
2370
|
assert.isUndefined(meeting.llmHealthCheckTimer);
|
|
2269
2371
|
|
|
2270
|
-
// Fast forward time to ensure no metric is sent
|
|
2271
2372
|
Metrics.sendBehavioralMetric.resetHistory();
|
|
2272
2373
|
fakeClock.tick(3 * 60 * 1000);
|
|
2273
2374
|
|
|
@@ -2302,7 +2403,6 @@ describe('plugin-meetings', () => {
|
|
|
2302
2403
|
.stub()
|
|
2303
2404
|
.rejects(new CaptchaError('bad captcha'));
|
|
2304
2405
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2305
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil, 'joinMeetingOptions');
|
|
2306
2406
|
|
|
2307
2407
|
try {
|
|
2308
2408
|
await meeting.join();
|
|
@@ -2316,8 +2416,7 @@ describe('plugin-meetings', () => {
|
|
|
2316
2416
|
);
|
|
2317
2417
|
assert.instanceOf(error, CaptchaError);
|
|
2318
2418
|
assert.equal(error.message, 'bad captcha');
|
|
2319
|
-
|
|
2320
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2419
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2321
2420
|
}
|
|
2322
2421
|
});
|
|
2323
2422
|
|
|
@@ -2326,7 +2425,6 @@ describe('plugin-meetings', () => {
|
|
|
2326
2425
|
.stub()
|
|
2327
2426
|
.rejects(new PasswordError('bad password'));
|
|
2328
2427
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2329
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2330
2428
|
|
|
2331
2429
|
try {
|
|
2332
2430
|
await meeting.join();
|
|
@@ -2340,8 +2438,7 @@ describe('plugin-meetings', () => {
|
|
|
2340
2438
|
);
|
|
2341
2439
|
assert.instanceOf(error, PasswordError);
|
|
2342
2440
|
assert.equal(error.message, 'bad password');
|
|
2343
|
-
|
|
2344
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2441
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2345
2442
|
}
|
|
2346
2443
|
});
|
|
2347
2444
|
|
|
@@ -2350,7 +2447,6 @@ describe('plugin-meetings', () => {
|
|
|
2350
2447
|
.stub()
|
|
2351
2448
|
.rejects(new PermissionError('bad permission'));
|
|
2352
2449
|
const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
|
|
2353
|
-
const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
|
|
2354
2450
|
|
|
2355
2451
|
try {
|
|
2356
2452
|
await meeting.join();
|
|
@@ -2364,14 +2460,14 @@ describe('plugin-meetings', () => {
|
|
|
2364
2460
|
);
|
|
2365
2461
|
assert.instanceOf(error, PermissionError);
|
|
2366
2462
|
assert.equal(error.message, 'bad permission');
|
|
2367
|
-
|
|
2368
|
-
assert.notCalled(joinMeetingOptionsSpy);
|
|
2463
|
+
assert.notCalled(MeetingUtil.joinMeeting);
|
|
2369
2464
|
}
|
|
2370
2465
|
});
|
|
2371
2466
|
});
|
|
2372
2467
|
});
|
|
2373
2468
|
});
|
|
2374
2469
|
|
|
2470
|
+
|
|
2375
2471
|
describe('#addMedia', () => {
|
|
2376
2472
|
const muteStateStub = {
|
|
2377
2473
|
handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
|
|
@@ -3028,6 +3124,111 @@ describe('plugin-meetings', () => {
|
|
|
3028
3124
|
checkWorking({allowMediaInLobby: true});
|
|
3029
3125
|
});
|
|
3030
3126
|
|
|
3127
|
+
const setupLobbyTest = () => {
|
|
3128
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
3129
|
+
.stub()
|
|
3130
|
+
.resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
|
3131
|
+
|
|
3132
|
+
meeting.meetingState = 'ACTIVE';
|
|
3133
|
+
meeting.locusInfo.parsedLocus = {self: {state: 'IDLE'}};
|
|
3134
|
+
meeting.isUserUnadmitted = true;
|
|
3135
|
+
|
|
3136
|
+
// Mock locusMediaRequest
|
|
3137
|
+
meeting.locusMediaRequest = {
|
|
3138
|
+
send: sinon.stub().resolves(),
|
|
3139
|
+
isConfluenceCreated: sinon.stub().returns(false),
|
|
3140
|
+
};
|
|
3141
|
+
|
|
3142
|
+
sinon.stub(RemoteMediaManagerModule, 'RemoteMediaManager').returns({
|
|
3143
|
+
start: sinon.stub().resolves(),
|
|
3144
|
+
on: sinon.stub(),
|
|
3145
|
+
logAllReceiveSlots: sinon.stub(),
|
|
3146
|
+
});
|
|
3147
|
+
|
|
3148
|
+
meeting.isMultistream = true;
|
|
3149
|
+
|
|
3150
|
+
const createFakeStream = (id) => ({
|
|
3151
|
+
on: sinon.stub(),
|
|
3152
|
+
off: sinon.stub(),
|
|
3153
|
+
userMuted: false,
|
|
3154
|
+
systemMuted: false,
|
|
3155
|
+
get muted() {
|
|
3156
|
+
return this.userMuted || this.systemMuted;
|
|
3157
|
+
},
|
|
3158
|
+
setUnmuteAllowed: sinon.stub(),
|
|
3159
|
+
setUserMuted: sinon.stub(),
|
|
3160
|
+
outputStream: {
|
|
3161
|
+
getTracks: () => [{id}],
|
|
3162
|
+
},
|
|
3163
|
+
getSettings: sinon.stub().returns({}),
|
|
3164
|
+
});
|
|
3165
|
+
|
|
3166
|
+
return {
|
|
3167
|
+
fakeMicrophoneStream: createFakeStream('fake mic'),
|
|
3168
|
+
fakeCameraStream: createFakeStream('fake camera'),
|
|
3169
|
+
};
|
|
3170
|
+
};
|
|
3171
|
+
|
|
3172
|
+
it('should not publish any local streams when in the lobby and allowPublishMediaInLobby is false', async () => {
|
|
3173
|
+
const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
|
|
3174
|
+
|
|
3175
|
+
const publishStreamStub = sinon.stub();
|
|
3176
|
+
fakeMediaConnection.createSendSlot = sinon.stub().returns({
|
|
3177
|
+
publishStream: publishStreamStub,
|
|
3178
|
+
unpublishStream: sinon.stub(),
|
|
3179
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3180
|
+
});
|
|
3181
|
+
|
|
3182
|
+
await meeting.addMedia({
|
|
3183
|
+
allowMediaInLobby: true,
|
|
3184
|
+
allowPublishMediaInLobby: false,
|
|
3185
|
+
audioEnabled: true,
|
|
3186
|
+
videoEnabled: true,
|
|
3187
|
+
localStreams: {
|
|
3188
|
+
microphone: fakeMicrophoneStream,
|
|
3189
|
+
camera: fakeCameraStream,
|
|
3190
|
+
},
|
|
3191
|
+
});
|
|
3192
|
+
|
|
3193
|
+
assert.notCalled(publishStreamStub);
|
|
3194
|
+
});
|
|
3195
|
+
|
|
3196
|
+
it('should publish local streams when in the lobby and allowPublishMediaInLobby is true', async () => {
|
|
3197
|
+
const {fakeMicrophoneStream, fakeCameraStream} = setupLobbyTest();
|
|
3198
|
+
|
|
3199
|
+
const audioSlot = {
|
|
3200
|
+
publishStream: sinon.stub(),
|
|
3201
|
+
unpublishStream: sinon.stub(),
|
|
3202
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3203
|
+
};
|
|
3204
|
+
const videoSlot = {
|
|
3205
|
+
publishStream: sinon.stub(),
|
|
3206
|
+
unpublishStream: sinon.stub(),
|
|
3207
|
+
setNamedMediaGroups: sinon.stub(),
|
|
3208
|
+
};
|
|
3209
|
+
|
|
3210
|
+
fakeMediaConnection.createSendSlot = sinon.stub().callsFake((mediaType) => {
|
|
3211
|
+
if (mediaType === 'AUDIO-MAIN') {
|
|
3212
|
+
return audioSlot;
|
|
3213
|
+
}
|
|
3214
|
+
return videoSlot;
|
|
3215
|
+
});
|
|
3216
|
+
|
|
3217
|
+
await meeting.addMedia({
|
|
3218
|
+
allowMediaInLobby: true,
|
|
3219
|
+
allowPublishMediaInLobby: true,
|
|
3220
|
+
audioEnabled: true,
|
|
3221
|
+
videoEnabled: true,
|
|
3222
|
+
localStreams: {
|
|
3223
|
+
microphone: fakeMicrophoneStream,
|
|
3224
|
+
camera: fakeCameraStream,
|
|
3225
|
+
},
|
|
3226
|
+
});
|
|
3227
|
+
|
|
3228
|
+
assert.calledOnceWithExactly(audioSlot.publishStream, fakeMicrophoneStream);
|
|
3229
|
+
assert.calledOnceWithExactly(videoSlot.publishStream, fakeCameraStream);
|
|
3230
|
+
});
|
|
3231
|
+
|
|
3031
3232
|
it('should create rtcMetrics and pass them to Media.createMediaConnection()', async () => {
|
|
3032
3233
|
const setIntervalOriginal = window.setInterval;
|
|
3033
3234
|
window.setInterval = sinon.stub().returns(1);
|
|
@@ -6218,7 +6419,10 @@ describe('plugin-meetings', () => {
|
|
|
6218
6419
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
6219
6420
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
6220
6421
|
meeting.logger.error = sinon.stub().returns(true);
|
|
6221
|
-
meeting.
|
|
6422
|
+
meeting.clearMeetingData = sinon.stub().callsFake(async () => {
|
|
6423
|
+
meeting.audio = null;
|
|
6424
|
+
meeting.video = null;
|
|
6425
|
+
});
|
|
6222
6426
|
webex.internal.voicea.off = sinon.stub().returns(true);
|
|
6223
6427
|
meeting.stopTranscription = sinon.stub();
|
|
6224
6428
|
meeting.transcription = {};
|
|
@@ -6245,9 +6449,7 @@ describe('plugin-meetings', () => {
|
|
|
6245
6449
|
assert.calledOnce(meeting.closePeerConnections);
|
|
6246
6450
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
|
6247
6451
|
assert.calledOnce(meeting.unsetPeerConnections);
|
|
6248
|
-
assert.calledOnce(meeting.
|
|
6249
|
-
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
6250
|
-
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
6452
|
+
assert.calledOnce(meeting.clearMeetingData);
|
|
6251
6453
|
});
|
|
6252
6454
|
|
|
6253
6455
|
it('should reset call diagnostic latencies correctly', async () => {
|
|
@@ -8248,7 +8450,10 @@ describe('plugin-meetings', () => {
|
|
|
8248
8450
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
8249
8451
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
8250
8452
|
meeting.logger.error = sinon.stub().returns(true);
|
|
8251
|
-
meeting.
|
|
8453
|
+
meeting.clearMeetingData = sinon.stub().callsFake(async () => {
|
|
8454
|
+
meeting.audio = null;
|
|
8455
|
+
meeting.video = null;
|
|
8456
|
+
});
|
|
8252
8457
|
meeting.transcription = {};
|
|
8253
8458
|
meeting.stopTranscription = sinon.stub();
|
|
8254
8459
|
|
|
@@ -8274,10 +8479,7 @@ describe('plugin-meetings', () => {
|
|
|
8274
8479
|
assert.calledOnce(meeting?.closePeerConnections);
|
|
8275
8480
|
assert.calledOnce(meeting?.unsetRemoteStreams);
|
|
8276
8481
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
8277
|
-
assert.calledOnce(meeting?.
|
|
8278
|
-
|
|
8279
|
-
assert.called(meeting.annotation.deregisterEvents);
|
|
8280
|
-
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
8482
|
+
assert.calledOnce(meeting?.clearMeetingData);
|
|
8281
8483
|
});
|
|
8282
8484
|
});
|
|
8283
8485
|
|
|
@@ -9014,8 +9216,8 @@ describe('plugin-meetings', () => {
|
|
|
9014
9216
|
const fakeMultistreamRoapMediaConnection = {
|
|
9015
9217
|
createSendSlot: () => {
|
|
9016
9218
|
return {
|
|
9017
|
-
|
|
9018
|
-
|
|
9219
|
+
setCustomCodecParameters: sinon.stub().resolves(),
|
|
9220
|
+
markCustomCodecParametersForDeletion: sinon.stub().resolves(),
|
|
9019
9221
|
};
|
|
9020
9222
|
},
|
|
9021
9223
|
};
|
|
@@ -9038,27 +9240,29 @@ describe('plugin-meetings', () => {
|
|
|
9038
9240
|
}
|
|
9039
9241
|
);
|
|
9040
9242
|
|
|
9041
|
-
it('should set
|
|
9243
|
+
it('should set custom codec parameters when shouldEnableMusicMode is true', async () => {
|
|
9042
9244
|
await meeting.enableMusicMode(true);
|
|
9043
9245
|
assert.calledOnceWithExactly(
|
|
9044
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9246
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters,
|
|
9247
|
+
MediaCodecMimeType.OPUS,
|
|
9045
9248
|
{
|
|
9046
9249
|
maxaveragebitrate: '64000',
|
|
9047
9250
|
maxplaybackrate: '48000',
|
|
9048
9251
|
}
|
|
9049
9252
|
);
|
|
9050
9253
|
assert.notCalled(
|
|
9051
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9254
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion
|
|
9052
9255
|
);
|
|
9053
9256
|
});
|
|
9054
9257
|
|
|
9055
|
-
it('should
|
|
9258
|
+
it('should mark custom codec parameters for deletion when shouldEnableMusicMode is false', async () => {
|
|
9056
9259
|
await meeting.enableMusicMode(false);
|
|
9057
9260
|
assert.calledOnceWithExactly(
|
|
9058
|
-
meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9261
|
+
meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion,
|
|
9262
|
+
MediaCodecMimeType.OPUS,
|
|
9059
9263
|
['maxaveragebitrate', 'maxplaybackrate']
|
|
9060
9264
|
);
|
|
9061
|
-
assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).
|
|
9265
|
+
assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters);
|
|
9062
9266
|
});
|
|
9063
9267
|
});
|
|
9064
9268
|
|
|
@@ -9149,7 +9353,10 @@ describe('plugin-meetings', () => {
|
|
|
9149
9353
|
|
|
9150
9354
|
// check that the right things were called by the callback
|
|
9151
9355
|
assert.calledOnceWithExactly(meeting.waitForRemoteSDPAnswer);
|
|
9152
|
-
assert.calledOnceWithExactly(
|
|
9356
|
+
assert.calledOnceWithExactly(
|
|
9357
|
+
meeting.mediaProperties.waitForMediaConnectionConnected,
|
|
9358
|
+
meeting.correlationId
|
|
9359
|
+
);
|
|
9153
9360
|
});
|
|
9154
9361
|
});
|
|
9155
9362
|
|
|
@@ -10210,14 +10417,24 @@ describe('plugin-meetings', () => {
|
|
|
10210
10417
|
);
|
|
10211
10418
|
done();
|
|
10212
10419
|
});
|
|
10213
|
-
it('listens to the self admitted guest event', (
|
|
10420
|
+
it('listens to the self admitted guest event without blocking on token prefetch', async () => {
|
|
10214
10421
|
meeting.stopKeepAlive = sinon.stub();
|
|
10215
10422
|
meeting.updateLLMConnection = sinon.stub();
|
|
10423
|
+
let resolvePrefetch;
|
|
10424
|
+
|
|
10425
|
+
meeting.ensureDefaultDatachannelTokenAfterAdmit = sinon
|
|
10426
|
+
.stub()
|
|
10427
|
+
.returns(new Promise((resolve) => {
|
|
10428
|
+
resolvePrefetch = resolve;
|
|
10429
|
+
}));
|
|
10216
10430
|
meeting.rtcMetrics = {
|
|
10217
10431
|
sendNextMetrics: sinon.stub(),
|
|
10218
10432
|
};
|
|
10433
|
+
|
|
10219
10434
|
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
10435
|
+
|
|
10220
10436
|
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
10437
|
+
assert.calledOnceWithExactly(meeting.ensureDefaultDatachannelTokenAfterAdmit);
|
|
10221
10438
|
assert.calledThrice(TriggerProxy.trigger);
|
|
10222
10439
|
assert.calledWith(
|
|
10223
10440
|
TriggerProxy.trigger,
|
|
@@ -10236,7 +10453,11 @@ describe('plugin-meetings', () => {
|
|
|
10236
10453
|
correlation_id: meeting.correlationId,
|
|
10237
10454
|
}
|
|
10238
10455
|
);
|
|
10239
|
-
|
|
10456
|
+
|
|
10457
|
+
resolvePrefetch(false);
|
|
10458
|
+
await Promise.resolve();
|
|
10459
|
+
|
|
10460
|
+
assert.calledOnce(meeting.updateLLMConnection);
|
|
10240
10461
|
});
|
|
10241
10462
|
|
|
10242
10463
|
it('listens to the breakouts changed event', () => {
|
|
@@ -10323,6 +10544,21 @@ describe('plugin-meetings', () => {
|
|
|
10323
10544
|
EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
|
|
10324
10545
|
);
|
|
10325
10546
|
});
|
|
10547
|
+
|
|
10548
|
+
it('listens to the self id changed event and updates aiEnableRequest', () => {
|
|
10549
|
+
meeting.aiEnableRequest = {
|
|
10550
|
+
selfParticipantIdUpdate: sinon.stub(),
|
|
10551
|
+
};
|
|
10552
|
+
|
|
10553
|
+
const payload = {selfId: 'participant-test-123'};
|
|
10554
|
+
|
|
10555
|
+
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ID_CHANGED', payload);
|
|
10556
|
+
|
|
10557
|
+
assert.calledOnceWithExactly(
|
|
10558
|
+
meeting.aiEnableRequest.selfParticipantIdUpdate,
|
|
10559
|
+
payload.selfId
|
|
10560
|
+
);
|
|
10561
|
+
});
|
|
10326
10562
|
});
|
|
10327
10563
|
|
|
10328
10564
|
describe('#setUpBreakoutsListener', () => {
|
|
@@ -10570,6 +10806,24 @@ describe('plugin-meetings', () => {
|
|
|
10570
10806
|
);
|
|
10571
10807
|
});
|
|
10572
10808
|
|
|
10809
|
+
it('listens to MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED', async () => {
|
|
10810
|
+
const aiSummaryNotification = {example: 'value'};
|
|
10811
|
+
|
|
10812
|
+
await meeting.locusInfo.emitScoped(
|
|
10813
|
+
{function: 'test', file: 'test'},
|
|
10814
|
+
LOCUSINFO.EVENTS.CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
10815
|
+
{aiSummaryNotification}
|
|
10816
|
+
);
|
|
10817
|
+
|
|
10818
|
+
assert.calledWith(
|
|
10819
|
+
TriggerProxy.trigger,
|
|
10820
|
+
meeting,
|
|
10821
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
10822
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_AI_SUMMARY_NOTIFICATION_UPDATED,
|
|
10823
|
+
{aiSummaryNotification}
|
|
10824
|
+
);
|
|
10825
|
+
});
|
|
10826
|
+
|
|
10573
10827
|
it('listens to MEETING_CONTROLS_MEETING_FULL_UPDATED', async () => {
|
|
10574
10828
|
const state = {example: 'value'};
|
|
10575
10829
|
|
|
@@ -10842,6 +11096,9 @@ describe('plugin-meetings', () => {
|
|
|
10842
11096
|
meeting.simultaneousInterpretation = {
|
|
10843
11097
|
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
10844
11098
|
};
|
|
11099
|
+
meeting.aiEnableRequest = {
|
|
11100
|
+
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
11101
|
+
};
|
|
10845
11102
|
|
|
10846
11103
|
meeting.locusInfo.emit(
|
|
10847
11104
|
{function: 'test', file: 'test'},
|
|
@@ -10861,6 +11118,10 @@ describe('plugin-meetings', () => {
|
|
|
10861
11118
|
meeting.simultaneousInterpretation.approvalUrlUpdate,
|
|
10862
11119
|
newLocusServices.services.approval.url
|
|
10863
11120
|
);
|
|
11121
|
+
assert.calledWith(
|
|
11122
|
+
meeting.aiEnableRequest.approvalUrlUpdate,
|
|
11123
|
+
newLocusServices.services.approval.url
|
|
11124
|
+
);
|
|
10864
11125
|
assert.calledOnce(meeting.recordingController.setSessionId);
|
|
10865
11126
|
done();
|
|
10866
11127
|
});
|
|
@@ -11266,6 +11527,41 @@ describe('plugin-meetings', () => {
|
|
|
11266
11527
|
});
|
|
11267
11528
|
});
|
|
11268
11529
|
|
|
11530
|
+
describe('localConstraintsChangeHandler', () => {
|
|
11531
|
+
it('calls updatePreferredBitrateKbps when not multistream', () => {
|
|
11532
|
+
meeting.isMultistream = false;
|
|
11533
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
11534
|
+
updatePreferredBitrateKbps: sinon.stub(),
|
|
11535
|
+
};
|
|
11536
|
+
|
|
11537
|
+
meeting.localConstraintsChangeHandler();
|
|
11538
|
+
|
|
11539
|
+
assert.calledOnce(
|
|
11540
|
+
meeting.mediaProperties.webrtcMediaConnection.updatePreferredBitrateKbps
|
|
11541
|
+
);
|
|
11542
|
+
});
|
|
11543
|
+
|
|
11544
|
+
it('does not call updatePreferredBitrateKbps when multistream', () => {
|
|
11545
|
+
meeting.isMultistream = true;
|
|
11546
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
11547
|
+
updatePreferredBitrateKbps: sinon.stub(),
|
|
11548
|
+
};
|
|
11549
|
+
|
|
11550
|
+
meeting.localConstraintsChangeHandler();
|
|
11551
|
+
|
|
11552
|
+
assert.notCalled(
|
|
11553
|
+
meeting.mediaProperties.webrtcMediaConnection.updatePreferredBitrateKbps
|
|
11554
|
+
);
|
|
11555
|
+
});
|
|
11556
|
+
|
|
11557
|
+
it('does not throw when webrtcMediaConnection is undefined', () => {
|
|
11558
|
+
meeting.isMultistream = false;
|
|
11559
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
11560
|
+
|
|
11561
|
+
assert.doesNotThrow(() => meeting.localConstraintsChangeHandler());
|
|
11562
|
+
});
|
|
11563
|
+
});
|
|
11564
|
+
|
|
11269
11565
|
describe('#parseMeetingInfo', () => {
|
|
11270
11566
|
const checkParseMeetingInfo = (expectedInfoToParse) => {
|
|
11271
11567
|
assert.equal(meeting.conversationUrl, expectedInfoToParse.conversationUrl);
|
|
@@ -11645,6 +11941,7 @@ describe('plugin-meetings', () => {
|
|
|
11645
11941
|
let canUnsetDisallowUnmuteSpy;
|
|
11646
11942
|
let canUserRaiseHandSpy;
|
|
11647
11943
|
let bothLeaveAndEndMeetingAvailableSpy;
|
|
11944
|
+
let requireHostEndMeetingBeforeLeaveSpy;
|
|
11648
11945
|
let canUserLowerAllHandsSpy;
|
|
11649
11946
|
let canUserLowerSomeoneElsesHandSpy;
|
|
11650
11947
|
let waitingForOthersToJoinSpy;
|
|
@@ -11656,6 +11953,8 @@ describe('plugin-meetings', () => {
|
|
|
11656
11953
|
let canMoveToLobbySpy;
|
|
11657
11954
|
let isSpokenLanguageAutoDetectionEnabledSpy;
|
|
11658
11955
|
let showAutoEndMeetingWarningSpy;
|
|
11956
|
+
let canAttendeeRequestAiAssistantEnabledSpy;
|
|
11957
|
+
let attendeeRequestAiAssistantDeclinedAllSpy;
|
|
11659
11958
|
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
|
11660
11959
|
|
|
11661
11960
|
beforeEach(() => {
|
|
@@ -11676,6 +11975,10 @@ describe('plugin-meetings', () => {
|
|
|
11676
11975
|
MeetingUtil,
|
|
11677
11976
|
'bothLeaveAndEndMeetingAvailable'
|
|
11678
11977
|
);
|
|
11978
|
+
requireHostEndMeetingBeforeLeaveSpy = sinon.spy(
|
|
11979
|
+
MeetingUtil,
|
|
11980
|
+
'requireHostEndMeetingBeforeLeave'
|
|
11981
|
+
);
|
|
11679
11982
|
canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
|
|
11680
11983
|
waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
|
|
11681
11984
|
canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
|
|
@@ -11692,12 +11995,22 @@ describe('plugin-meetings', () => {
|
|
|
11692
11995
|
MeetingUtil,
|
|
11693
11996
|
'isSpokenLanguageAutoDetectionEnabled'
|
|
11694
11997
|
);
|
|
11998
|
+
canAttendeeRequestAiAssistantEnabledSpy = sinon.spy(
|
|
11999
|
+
MeetingUtil,
|
|
12000
|
+
'canAttendeeRequestAiAssistantEnabled'
|
|
12001
|
+
);
|
|
12002
|
+
attendeeRequestAiAssistantDeclinedAllSpy = sinon.spy(
|
|
12003
|
+
MeetingUtil,
|
|
12004
|
+
'attendeeRequestAiAssistantDeclinedAll'
|
|
12005
|
+
);
|
|
11695
12006
|
});
|
|
11696
12007
|
|
|
11697
12008
|
afterEach(() => {
|
|
11698
12009
|
inMeetingActionsSetSpy.restore();
|
|
11699
12010
|
waitingForOthersToJoinSpy.restore();
|
|
11700
12011
|
showAutoEndMeetingWarningSpy.restore();
|
|
12012
|
+
canAttendeeRequestAiAssistantEnabledSpy.restore();
|
|
12013
|
+
attendeeRequestAiAssistantDeclinedAllSpy.restore();
|
|
11701
12014
|
});
|
|
11702
12015
|
|
|
11703
12016
|
forEach(
|
|
@@ -12221,6 +12534,7 @@ describe('plugin-meetings', () => {
|
|
|
12221
12534
|
const userDisplayHints = ['LOCK_CONTROL_UNLOCK'];
|
|
12222
12535
|
meeting.userDisplayHints = ['LOCK_CONTROL_UNLOCK'];
|
|
12223
12536
|
meeting.meetingInfo.supportVoIP = true;
|
|
12537
|
+
meeting.roles = [];
|
|
12224
12538
|
|
|
12225
12539
|
meeting.updateMeetingActions();
|
|
12226
12540
|
|
|
@@ -12236,6 +12550,7 @@ describe('plugin-meetings', () => {
|
|
|
12236
12550
|
assert.calledWith(canUnsetDisallowUnmuteSpy, userDisplayHints);
|
|
12237
12551
|
assert.calledWith(canUserRaiseHandSpy, userDisplayHints);
|
|
12238
12552
|
assert.calledWith(bothLeaveAndEndMeetingAvailableSpy, userDisplayHints);
|
|
12553
|
+
assert.calledWith(requireHostEndMeetingBeforeLeaveSpy, userDisplayHints);
|
|
12239
12554
|
assert.calledWith(canUserLowerAllHandsSpy, userDisplayHints);
|
|
12240
12555
|
assert.calledWith(canUserLowerSomeoneElsesHandSpy, userDisplayHints);
|
|
12241
12556
|
assert.calledWith(waitingForOthersToJoinSpy, userDisplayHints);
|
|
@@ -12247,6 +12562,12 @@ describe('plugin-meetings', () => {
|
|
|
12247
12562
|
assert.calledWith(canMoveToLobbySpy, userDisplayHints);
|
|
12248
12563
|
assert.calledWith(showAutoEndMeetingWarningSpy, userDisplayHints);
|
|
12249
12564
|
assert.calledWith(isSpokenLanguageAutoDetectionEnabledSpy, userDisplayHints);
|
|
12565
|
+
assert.calledWith(
|
|
12566
|
+
canAttendeeRequestAiAssistantEnabledSpy,
|
|
12567
|
+
userDisplayHints,
|
|
12568
|
+
meeting.roles
|
|
12569
|
+
);
|
|
12570
|
+
assert.calledWith(attendeeRequestAiAssistantDeclinedAllSpy, userDisplayHints);
|
|
12250
12571
|
|
|
12251
12572
|
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
12252
12573
|
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
@@ -12389,33 +12710,159 @@ describe('plugin-meetings', () => {
|
|
|
12389
12710
|
|
|
12390
12711
|
describe('#handleDataChannelUrlChange', () => {
|
|
12391
12712
|
let updateLLMConnectionSpy;
|
|
12713
|
+
let updatePSDataChannelSpy;
|
|
12392
12714
|
|
|
12393
12715
|
beforeEach(() => {
|
|
12394
12716
|
updateLLMConnectionSpy = sinon.spy(meeting, 'updateLLMConnection');
|
|
12717
|
+
updatePSDataChannelSpy = sinon.stub(meeting.webinar, 'updatePSDataChannel').resolves();
|
|
12718
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
12395
12719
|
});
|
|
12396
12720
|
|
|
12397
|
-
const check = (
|
|
12398
|
-
|
|
12721
|
+
const check = (
|
|
12722
|
+
url,
|
|
12723
|
+
practiceSessionDatachannelUrl,
|
|
12724
|
+
{expectedMainCalled, expectedPracticeCalled}
|
|
12725
|
+
) => {
|
|
12726
|
+
meeting.handleDataChannelUrlChange(url, practiceSessionDatachannelUrl);
|
|
12399
12727
|
|
|
12400
|
-
if (
|
|
12728
|
+
if (expectedMainCalled) {
|
|
12401
12729
|
assert.calledWith(updateLLMConnectionSpy);
|
|
12402
12730
|
} else {
|
|
12403
12731
|
assert.notCalled(updateLLMConnectionSpy);
|
|
12404
12732
|
}
|
|
12733
|
+
|
|
12734
|
+
if (expectedPracticeCalled) {
|
|
12735
|
+
assert.calledWith(updatePSDataChannelSpy);
|
|
12736
|
+
} else {
|
|
12737
|
+
assert.notCalled(updatePSDataChannelSpy);
|
|
12738
|
+
}
|
|
12405
12739
|
};
|
|
12406
12740
|
|
|
12407
12741
|
it('calls deferred updateLLMConnection if datachannelURL is set and the enableAutomaticLLM is true', () => {
|
|
12408
12742
|
meeting.config.enableAutomaticLLM = true;
|
|
12409
|
-
check('some url', true);
|
|
12743
|
+
check('some url', undefined, {expectedMainCalled: true, expectedPracticeCalled: false});
|
|
12410
12744
|
});
|
|
12411
12745
|
|
|
12412
12746
|
it('does not call updateLLMConnection if datachannelURL is undefined', () => {
|
|
12413
12747
|
meeting.config.enableAutomaticLLM = true;
|
|
12414
|
-
check(undefined,
|
|
12748
|
+
check(undefined, undefined, {
|
|
12749
|
+
expectedMainCalled: false,
|
|
12750
|
+
expectedPracticeCalled: false,
|
|
12751
|
+
});
|
|
12415
12752
|
});
|
|
12416
12753
|
|
|
12417
12754
|
it('does not call updateLLMConnection if enableAutomaticLLM is false', () => {
|
|
12418
|
-
check('some url',
|
|
12755
|
+
check('some url', 'some practice url', {
|
|
12756
|
+
expectedMainCalled: false,
|
|
12757
|
+
expectedPracticeCalled: false,
|
|
12758
|
+
});
|
|
12759
|
+
});
|
|
12760
|
+
|
|
12761
|
+
it('calls updatePSDataChannel when practice-session routing is active', () => {
|
|
12762
|
+
meeting.config.enableAutomaticLLM = true;
|
|
12763
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
12764
|
+
|
|
12765
|
+
check('some url', 'some practice url', {
|
|
12766
|
+
expectedMainCalled: true,
|
|
12767
|
+
expectedPracticeCalled: true,
|
|
12768
|
+
});
|
|
12769
|
+
});
|
|
12770
|
+
|
|
12771
|
+
it('does not call updatePSDataChannel when the main datachannelURL is undefined', () => {
|
|
12772
|
+
meeting.config.enableAutomaticLLM = true;
|
|
12773
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
12774
|
+
|
|
12775
|
+
check(undefined, 'some practice url', {
|
|
12776
|
+
expectedMainCalled: false,
|
|
12777
|
+
expectedPracticeCalled: false,
|
|
12778
|
+
});
|
|
12779
|
+
});
|
|
12780
|
+
});
|
|
12781
|
+
|
|
12782
|
+
describe('#saveDataChannelToken', () => {
|
|
12783
|
+
beforeEach(() => {
|
|
12784
|
+
webex.internal.llm.setDatachannelToken = sinon.stub();
|
|
12785
|
+
});
|
|
12786
|
+
|
|
12787
|
+
it('saves datachannelToken into LLM as Default', () => {
|
|
12788
|
+
meeting.saveDataChannelToken({
|
|
12789
|
+
locus: {
|
|
12790
|
+
self: {datachannelToken: 'default-token'},
|
|
12791
|
+
},
|
|
12792
|
+
});
|
|
12793
|
+
|
|
12794
|
+
assert.calledWithExactly(
|
|
12795
|
+
webex.internal.llm.setDatachannelToken,
|
|
12796
|
+
'default-token',
|
|
12797
|
+
'llm-default-session'
|
|
12798
|
+
);
|
|
12799
|
+
});
|
|
12800
|
+
|
|
12801
|
+
it('saves practiceSessionDatachannelToken into LLM as PracticeSession', () => {
|
|
12802
|
+
meeting.saveDataChannelToken({
|
|
12803
|
+
locus: {
|
|
12804
|
+
self: {practiceSessionDatachannelToken: 'ps-token'},
|
|
12805
|
+
},
|
|
12806
|
+
});
|
|
12807
|
+
|
|
12808
|
+
assert.calledWithExactly(
|
|
12809
|
+
webex.internal.llm.setDatachannelToken,
|
|
12810
|
+
'ps-token',
|
|
12811
|
+
'llm-practice-session'
|
|
12812
|
+
);
|
|
12813
|
+
});
|
|
12814
|
+
|
|
12815
|
+
it('saves both tokens when both are present', () => {
|
|
12816
|
+
meeting.saveDataChannelToken({
|
|
12817
|
+
locus: {
|
|
12818
|
+
self: {
|
|
12819
|
+
datachannelToken: 'default-token',
|
|
12820
|
+
practiceSessionDatachannelToken: 'ps-token',
|
|
12821
|
+
},
|
|
12822
|
+
},
|
|
12823
|
+
});
|
|
12824
|
+
|
|
12825
|
+
assert.calledTwice(webex.internal.llm.setDatachannelToken);
|
|
12826
|
+
assert.calledWithExactly(
|
|
12827
|
+
webex.internal.llm.setDatachannelToken,
|
|
12828
|
+
'default-token',
|
|
12829
|
+
'llm-default-session'
|
|
12830
|
+
);
|
|
12831
|
+
assert.calledWithExactly(
|
|
12832
|
+
webex.internal.llm.setDatachannelToken,
|
|
12833
|
+
'ps-token',
|
|
12834
|
+
'llm-practice-session'
|
|
12835
|
+
);
|
|
12836
|
+
});
|
|
12837
|
+
|
|
12838
|
+
it('does not call setDatachannelToken when no tokens are present', () => {
|
|
12839
|
+
meeting.saveDataChannelToken({locus: {self: {}}});
|
|
12840
|
+
|
|
12841
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12842
|
+
});
|
|
12843
|
+
|
|
12844
|
+
it('handles undefined join gracefully', () => {
|
|
12845
|
+
meeting.saveDataChannelToken(undefined);
|
|
12846
|
+
|
|
12847
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12848
|
+
});
|
|
12849
|
+
|
|
12850
|
+
it('handles missing locus.self gracefully', () => {
|
|
12851
|
+
meeting.saveDataChannelToken({locus: {}});
|
|
12852
|
+
|
|
12853
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
12854
|
+
});
|
|
12855
|
+
});
|
|
12856
|
+
|
|
12857
|
+
describe('#clearDataChannelToken', () => {
|
|
12858
|
+
beforeEach(() => {
|
|
12859
|
+
webex.internal.llm.resetDatachannelTokens = sinon.stub();
|
|
12860
|
+
});
|
|
12861
|
+
|
|
12862
|
+
it('calls resetDatachannelTokens on LLM', () => {
|
|
12863
|
+
meeting.clearDataChannelToken();
|
|
12864
|
+
|
|
12865
|
+
assert.calledOnce(webex.internal.llm.resetDatachannelTokens);
|
|
12419
12866
|
});
|
|
12420
12867
|
});
|
|
12421
12868
|
|
|
@@ -12424,16 +12871,20 @@ describe('plugin-meetings', () => {
|
|
|
12424
12871
|
webex.internal.llm.isConnected = sinon.stub().returns(false);
|
|
12425
12872
|
webex.internal.llm.getLocusUrl = sinon.stub();
|
|
12426
12873
|
webex.internal.llm.getDatachannelUrl = sinon.stub();
|
|
12427
|
-
webex.internal.llm.registerAndConnect = sinon
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
webex.internal.llm.
|
|
12431
|
-
|
|
12432
|
-
|
|
12874
|
+
webex.internal.llm.registerAndConnect = sinon.stub().resolves('something');
|
|
12875
|
+
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
12876
|
+
webex.internal.llm.on = sinon.stub();
|
|
12877
|
+
webex.internal.llm.off = sinon.stub();
|
|
12878
|
+
webex.internal.llm.getDatachannelToken = sinon.stub().returns(undefined);
|
|
12879
|
+
webex.internal.llm.setDatachannelToken = sinon.stub();
|
|
12880
|
+
|
|
12433
12881
|
meeting.processRelayEvent = sinon.stub();
|
|
12882
|
+
meeting.processLocusLLMEvent = sinon.stub();
|
|
12883
|
+
meeting.clearLLMHealthCheckTimer = sinon.stub();
|
|
12884
|
+
meeting.startLLMHealthCheckTimer = sinon.stub();
|
|
12885
|
+
|
|
12434
12886
|
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
12435
12887
|
});
|
|
12436
|
-
|
|
12437
12888
|
it('does not connect if the call is not joined yet', async () => {
|
|
12438
12889
|
meeting.joinedWith = {state: 'any other state'};
|
|
12439
12890
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
@@ -12447,31 +12898,21 @@ describe('plugin-meetings', () => {
|
|
|
12447
12898
|
assert.equal(result, undefined);
|
|
12448
12899
|
assert.notCalled(meeting.webex.internal.llm.on);
|
|
12449
12900
|
});
|
|
12450
|
-
|
|
12451
12901
|
it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
|
|
12452
12902
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
12458
|
-
|
|
12459
|
-
const result = await meeting.updateLLMConnection();
|
|
12460
|
-
|
|
12461
|
-
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12462
|
-
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12463
|
-
assert.equal(result, undefined);
|
|
12464
|
-
assert.notCalled(meeting.webex.internal.llm.on);
|
|
12465
|
-
});
|
|
12466
|
-
|
|
12467
|
-
it('connects if not already connected', async () => {
|
|
12468
|
-
meeting.joinedWith = {state: 'JOINED'};
|
|
12469
|
-
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
12903
|
+
meeting.locusInfo = {
|
|
12904
|
+
url: 'a url',
|
|
12905
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12906
|
+
};
|
|
12470
12907
|
|
|
12471
12908
|
const result = await meeting.updateLLMConnection();
|
|
12472
|
-
|
|
12473
12909
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12474
|
-
assert.
|
|
12910
|
+
assert.calledWithExactly(
|
|
12911
|
+
webex.internal.llm.registerAndConnect,
|
|
12912
|
+
'a url',
|
|
12913
|
+
'a datachannel url',
|
|
12914
|
+
undefined
|
|
12915
|
+
);
|
|
12475
12916
|
assert.equal(result, 'something');
|
|
12476
12917
|
assert.calledWithExactly(
|
|
12477
12918
|
meeting.webex.internal.llm.off,
|
|
@@ -12494,27 +12935,49 @@ describe('plugin-meetings', () => {
|
|
|
12494
12935
|
meeting.processLocusLLMEvent
|
|
12495
12936
|
);
|
|
12496
12937
|
});
|
|
12938
|
+
it('connects if not already connected', async () => {
|
|
12939
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
12940
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
12497
12941
|
|
|
12498
|
-
|
|
12942
|
+
const result = await meeting.updateLLMConnection();
|
|
12943
|
+
|
|
12944
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
12945
|
+
assert.calledWithExactly(
|
|
12946
|
+
webex.internal.llm.registerAndConnect,
|
|
12947
|
+
'a url',
|
|
12948
|
+
'a datachannel url',
|
|
12949
|
+
undefined
|
|
12950
|
+
);
|
|
12951
|
+
assert.equal(result, 'something');
|
|
12952
|
+
});
|
|
12953
|
+
it('disconnects if the locus url has changed', async () => {
|
|
12499
12954
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12955
|
+
|
|
12500
12956
|
webex.internal.llm.isConnected.returns(true);
|
|
12501
12957
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12502
|
-
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
12503
12958
|
|
|
12504
|
-
meeting.locusInfo = {
|
|
12959
|
+
meeting.locusInfo = {
|
|
12960
|
+
url: 'a different url',
|
|
12961
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
12962
|
+
self: {},
|
|
12963
|
+
};
|
|
12505
12964
|
|
|
12506
12965
|
const result = await meeting.updateLLMConnection();
|
|
12507
12966
|
|
|
12508
|
-
assert.
|
|
12967
|
+
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
12509
12968
|
code: 3050,
|
|
12510
12969
|
reason: 'done (permanent)',
|
|
12511
12970
|
});
|
|
12512
|
-
|
|
12971
|
+
|
|
12972
|
+
assert.calledWithExactly(
|
|
12513
12973
|
webex.internal.llm.registerAndConnect,
|
|
12514
12974
|
'a different url',
|
|
12515
|
-
'a datachannel url'
|
|
12975
|
+
'a datachannel url',
|
|
12976
|
+
undefined
|
|
12516
12977
|
);
|
|
12978
|
+
|
|
12517
12979
|
assert.equal(result, 'something');
|
|
12980
|
+
|
|
12518
12981
|
assert.calledWithExactly(
|
|
12519
12982
|
meeting.webex.internal.llm.off,
|
|
12520
12983
|
'event:relay.event',
|
|
@@ -12526,6 +12989,7 @@ describe('plugin-meetings', () => {
|
|
|
12526
12989
|
meeting.processLocusLLMEvent
|
|
12527
12990
|
);
|
|
12528
12991
|
assert.callCount(meeting.webex.internal.llm.off, 4);
|
|
12992
|
+
|
|
12529
12993
|
assert.calledWithExactly(
|
|
12530
12994
|
meeting.webex.internal.llm.on,
|
|
12531
12995
|
'event:relay.event',
|
|
@@ -12536,28 +13000,37 @@ describe('plugin-meetings', () => {
|
|
|
12536
13000
|
'event:locus.state_message',
|
|
12537
13001
|
meeting.processLocusLLMEvent
|
|
12538
13002
|
);
|
|
13003
|
+
assert.isFalse(
|
|
13004
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13005
|
+
);
|
|
12539
13006
|
});
|
|
12540
|
-
|
|
12541
|
-
it('disconnects it first if the data channel url has changed', async () => {
|
|
13007
|
+
it('disconnects if the data channel url has changed', async () => {
|
|
12542
13008
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12543
13009
|
webex.internal.llm.isConnected.returns(true);
|
|
12544
13010
|
webex.internal.llm.getLocusUrl.returns('a url');
|
|
12545
|
-
webex.internal.llm.getDatachannelUrl.returns('a datachannel url');
|
|
12546
13011
|
|
|
12547
|
-
meeting.locusInfo = {
|
|
13012
|
+
meeting.locusInfo = {
|
|
13013
|
+
url: 'a url',
|
|
13014
|
+
info: {datachannelUrl: 'a different datachannel url'},
|
|
13015
|
+
self: {},
|
|
13016
|
+
};
|
|
12548
13017
|
|
|
12549
13018
|
const result = await meeting.updateLLMConnection();
|
|
12550
13019
|
|
|
12551
|
-
assert.
|
|
13020
|
+
assert.calledWithExactly(webex.internal.llm.disconnectLLM, {
|
|
12552
13021
|
code: 3050,
|
|
12553
13022
|
reason: 'done (permanent)',
|
|
12554
13023
|
});
|
|
12555
|
-
|
|
13024
|
+
|
|
13025
|
+
assert.calledWithExactly(
|
|
12556
13026
|
webex.internal.llm.registerAndConnect,
|
|
12557
13027
|
'a url',
|
|
12558
|
-
'a different datachannel url'
|
|
13028
|
+
'a different datachannel url',
|
|
13029
|
+
undefined
|
|
12559
13030
|
);
|
|
13031
|
+
|
|
12560
13032
|
assert.equal(result, 'something');
|
|
13033
|
+
|
|
12561
13034
|
assert.calledWithExactly(
|
|
12562
13035
|
meeting.webex.internal.llm.off,
|
|
12563
13036
|
'event:relay.event',
|
|
@@ -12568,6 +13041,7 @@ describe('plugin-meetings', () => {
|
|
|
12568
13041
|
'event:locus.state_message',
|
|
12569
13042
|
meeting.processLocusLLMEvent
|
|
12570
13043
|
);
|
|
13044
|
+
|
|
12571
13045
|
assert.calledWithExactly(
|
|
12572
13046
|
meeting.webex.internal.llm.on,
|
|
12573
13047
|
'event:relay.event',
|
|
@@ -12578,8 +13052,10 @@ describe('plugin-meetings', () => {
|
|
|
12578
13052
|
'event:locus.state_message',
|
|
12579
13053
|
meeting.processLocusLLMEvent
|
|
12580
13054
|
);
|
|
13055
|
+
assert.isFalse(
|
|
13056
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13057
|
+
);
|
|
12581
13058
|
});
|
|
12582
|
-
|
|
12583
13059
|
it('disconnects when the state is not JOINED', async () => {
|
|
12584
13060
|
meeting.joinedWith = {state: 'any other state'};
|
|
12585
13061
|
webex.internal.llm.isConnected.returns(true);
|
|
@@ -12589,9 +13065,38 @@ describe('plugin-meetings', () => {
|
|
|
12589
13065
|
|
|
12590
13066
|
const result = await meeting.updateLLMConnection();
|
|
12591
13067
|
|
|
12592
|
-
assert.calledWith(webex.internal.llm.disconnectLLM,
|
|
13068
|
+
assert.calledWith(webex.internal.llm.disconnectLLM, {
|
|
13069
|
+
code: 3050,
|
|
13070
|
+
reason: 'done (permanent)',
|
|
13071
|
+
});
|
|
12593
13072
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12594
13073
|
assert.equal(result, undefined);
|
|
13074
|
+
assert.isFalse(
|
|
13075
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13076
|
+
);
|
|
13077
|
+
});
|
|
13078
|
+
it('rethrows disconnect errors during reconnect cleanup after removing relay listeners and timer', async () => {
|
|
13079
|
+
const disconnectError = new Error('disconnect failed');
|
|
13080
|
+
|
|
13081
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13082
|
+
webex.internal.llm.isConnected.returns(true);
|
|
13083
|
+
webex.internal.llm.getLocusUrl.returns('a url');
|
|
13084
|
+
webex.internal.llm.disconnectLLM.rejects(disconnectError);
|
|
13085
|
+
|
|
13086
|
+
meeting.locusInfo = {
|
|
13087
|
+
url: 'a different url',
|
|
13088
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13089
|
+
self: {},
|
|
13090
|
+
};
|
|
13091
|
+
|
|
13092
|
+
try {
|
|
13093
|
+
await meeting.updateLLMConnection();
|
|
13094
|
+
assert.fail('Expected updateLLMConnection to reject when disconnectLLM fails');
|
|
13095
|
+
} catch (error) {
|
|
13096
|
+
assert.equal(error, disconnectError);
|
|
13097
|
+
}
|
|
13098
|
+
|
|
13099
|
+
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
12595
13100
|
assert.calledWithExactly(
|
|
12596
13101
|
meeting.webex.internal.llm.off,
|
|
12597
13102
|
'event:relay.event',
|
|
@@ -12602,22 +13107,159 @@ describe('plugin-meetings', () => {
|
|
|
12602
13107
|
'event:locus.state_message',
|
|
12603
13108
|
meeting.processLocusLLMEvent
|
|
12604
13109
|
);
|
|
13110
|
+
assert.isFalse(
|
|
13111
|
+
meeting.webex.internal.llm.off.calledWithExactly('online', meeting.handleLLMOnline)
|
|
13112
|
+
);
|
|
13113
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
12605
13114
|
});
|
|
12606
|
-
|
|
12607
|
-
it('connect ps data channel if ps started in webinar', async () => {
|
|
13115
|
+
it('still need connect main session data channel when PS started', async () => {
|
|
12608
13116
|
meeting.joinedWith = {state: 'JOINED'};
|
|
12609
13117
|
meeting.locusInfo = {
|
|
12610
13118
|
url: 'a url',
|
|
12611
13119
|
info: {
|
|
12612
13120
|
datachannelUrl: 'a datachannel url',
|
|
12613
|
-
practiceSessionDatachannelUrl: '
|
|
13121
|
+
practiceSessionDatachannelUrl: 'ps-url',
|
|
12614
13122
|
},
|
|
12615
13123
|
};
|
|
12616
|
-
meeting.webinar.isJoinPracticeSessionDataChannel
|
|
13124
|
+
meeting.webinar.isJoinPracticeSessionDataChannel.returns(true);
|
|
13125
|
+
|
|
12617
13126
|
await meeting.updateLLMConnection();
|
|
12618
13127
|
|
|
12619
|
-
assert.
|
|
12620
|
-
|
|
13128
|
+
assert.calledWithExactly(
|
|
13129
|
+
webex.internal.llm.registerAndConnect,
|
|
13130
|
+
'a url',
|
|
13131
|
+
'a datachannel url',
|
|
13132
|
+
undefined
|
|
13133
|
+
);
|
|
13134
|
+
});
|
|
13135
|
+
it('passes dataChannelToken from LLM to registerAndConnect', async () => {
|
|
13136
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13137
|
+
meeting.locusInfo = {
|
|
13138
|
+
url: 'a url',
|
|
13139
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13140
|
+
};
|
|
13141
|
+
|
|
13142
|
+
webex.internal.llm.getDatachannelToken.withArgs('llm-default-session').returns('token-123');
|
|
13143
|
+
|
|
13144
|
+
await meeting.updateLLMConnection();
|
|
13145
|
+
|
|
13146
|
+
assert.calledWithExactly(
|
|
13147
|
+
webex.internal.llm.registerAndConnect,
|
|
13148
|
+
'a url',
|
|
13149
|
+
'a datachannel url',
|
|
13150
|
+
'token-123'
|
|
13151
|
+
);
|
|
13152
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13153
|
+
});
|
|
13154
|
+
it('passes undefined token when LLM has no token stored', async () => {
|
|
13155
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13156
|
+
meeting.locusInfo = {
|
|
13157
|
+
url: 'a url',
|
|
13158
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13159
|
+
};
|
|
13160
|
+
|
|
13161
|
+
webex.internal.llm.getDatachannelToken.returns(undefined);
|
|
13162
|
+
|
|
13163
|
+
await meeting.updateLLMConnection();
|
|
13164
|
+
|
|
13165
|
+
assert.calledWithExactly(
|
|
13166
|
+
webex.internal.llm.registerAndConnect,
|
|
13167
|
+
'a url',
|
|
13168
|
+
'a datachannel url',
|
|
13169
|
+
undefined
|
|
13170
|
+
);
|
|
13171
|
+
|
|
13172
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13173
|
+
});
|
|
13174
|
+
|
|
13175
|
+
it('does not pass token when data channel with jwt token is disabled', async () => {
|
|
13176
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
13177
|
+
meeting.locusInfo = {
|
|
13178
|
+
url: 'a url',
|
|
13179
|
+
info: {datachannelUrl: 'a datachannel url'},
|
|
13180
|
+
};
|
|
13181
|
+
|
|
13182
|
+
webex.internal.llm.getDatachannelToken.returns(undefined);
|
|
13183
|
+
webex.internal.llm.isDataChannelTokenEnabled = sinon.stub().resolves(false);
|
|
13184
|
+
|
|
13185
|
+
await meeting.updateLLMConnection();
|
|
13186
|
+
|
|
13187
|
+
assert.calledWithExactly(
|
|
13188
|
+
webex.internal.llm.registerAndConnect,
|
|
13189
|
+
'a url',
|
|
13190
|
+
'a datachannel url',
|
|
13191
|
+
undefined
|
|
13192
|
+
);
|
|
13193
|
+
assert.notCalled(webex.internal.llm.setDatachannelToken);
|
|
13194
|
+
});
|
|
13195
|
+
|
|
13196
|
+
describe('#clearMeetingData', () => {
|
|
13197
|
+
beforeEach(() => {
|
|
13198
|
+
webex.internal.llm.isConnected = sinon.stub().returns(true);
|
|
13199
|
+
webex.internal.llm.disconnectLLM = sinon.stub().resolves();
|
|
13200
|
+
webex.internal.llm.off = sinon.stub();
|
|
13201
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
13202
|
+
meeting.clearLLMHealthCheckTimer = sinon.stub();
|
|
13203
|
+
meeting.stopTranscription = sinon.stub();
|
|
13204
|
+
meeting.clearDataChannelToken = sinon.stub();
|
|
13205
|
+
meeting.shareStatus = 'no-share';
|
|
13206
|
+
});
|
|
13207
|
+
|
|
13208
|
+
it('disconnects llm and removes online and relay listeners during meeting data cleanup', async () => {
|
|
13209
|
+
await meeting.clearMeetingData();
|
|
13210
|
+
|
|
13211
|
+
assert.calledOnceWithExactly(webex.internal.llm.disconnectLLM, {
|
|
13212
|
+
code: 3050,
|
|
13213
|
+
reason: 'done (permanent)',
|
|
13214
|
+
});
|
|
13215
|
+
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
13216
|
+
assert.calledWithExactly(
|
|
13217
|
+
webex.internal.llm.off,
|
|
13218
|
+
'event:relay.event',
|
|
13219
|
+
meeting.processRelayEvent
|
|
13220
|
+
);
|
|
13221
|
+
assert.calledWithExactly(
|
|
13222
|
+
webex.internal.llm.off,
|
|
13223
|
+
'event:locus.state_message',
|
|
13224
|
+
meeting.processLocusLLMEvent
|
|
13225
|
+
);
|
|
13226
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13227
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13228
|
+
assert.isUndefined(meeting.transcription);
|
|
13229
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13230
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13231
|
+
});
|
|
13232
|
+
it('continues cleanup when disconnectLLM fails during meeting data cleanup', async () => {
|
|
13233
|
+
webex.internal.llm.disconnectLLM.rejects(new Error('disconnect failed'));
|
|
13234
|
+
|
|
13235
|
+
await meeting.clearMeetingData();
|
|
13236
|
+
|
|
13237
|
+
assert.calledWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
|
|
13238
|
+
assert.calledWithExactly(
|
|
13239
|
+
webex.internal.llm.off,
|
|
13240
|
+
'event:relay.event',
|
|
13241
|
+
meeting.processRelayEvent
|
|
13242
|
+
);
|
|
13243
|
+
assert.calledWithExactly(
|
|
13244
|
+
webex.internal.llm.off,
|
|
13245
|
+
'event:locus.state_message',
|
|
13246
|
+
meeting.processLocusLLMEvent
|
|
13247
|
+
);
|
|
13248
|
+
assert.calledOnce(meeting.clearLLMHealthCheckTimer);
|
|
13249
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13250
|
+
assert.isUndefined(meeting.transcription);
|
|
13251
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13252
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
13253
|
+
});
|
|
13254
|
+
it('always calls stopTranscription even when transcription is undefined', async () => {
|
|
13255
|
+
meeting.transcription = undefined;
|
|
13256
|
+
|
|
13257
|
+
await meeting.clearMeetingData();
|
|
13258
|
+
|
|
13259
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
13260
|
+
assert.isUndefined(meeting.transcription);
|
|
13261
|
+
assert.calledOnce(meeting.clearDataChannelToken);
|
|
13262
|
+
});
|
|
12621
13263
|
});
|
|
12622
13264
|
});
|
|
12623
13265
|
|
|
@@ -12628,6 +13270,7 @@ describe('plugin-meetings', () => {
|
|
|
12628
13270
|
|
|
12629
13271
|
it('should read the locus object, set on the meeting and return null', () => {
|
|
12630
13272
|
const dataSets = {someFakeStuff: 'dataSet'};
|
|
13273
|
+
const metadata = {some: 'metadata'};
|
|
12631
13274
|
|
|
12632
13275
|
meeting.setLocus({
|
|
12633
13276
|
mediaConnections: [test1],
|
|
@@ -12637,12 +13280,14 @@ describe('plugin-meetings', () => {
|
|
|
12637
13280
|
mediaId: uuid3,
|
|
12638
13281
|
locus: {host: {id: uuid4}},
|
|
12639
13282
|
dataSets,
|
|
13283
|
+
metadata,
|
|
12640
13284
|
});
|
|
12641
13285
|
assert.calledOnce(meeting.locusInfo.initialSetup);
|
|
12642
13286
|
assert.calledWith(meeting.locusInfo.initialSetup, {
|
|
12643
13287
|
trigger: 'join-response',
|
|
12644
13288
|
locus: {host: {id: uuid4}},
|
|
12645
13289
|
dataSets,
|
|
13290
|
+
metadata,
|
|
12646
13291
|
});
|
|
12647
13292
|
assert.equal(meeting.mediaConnections, test1);
|
|
12648
13293
|
assert.equal(meeting.locusUrl, url1);
|
|
@@ -14184,6 +14829,69 @@ describe('plugin-meetings', () => {
|
|
|
14184
14829
|
assert.calledOnce(meeting.meetingRequest.keepAlive);
|
|
14185
14830
|
});
|
|
14186
14831
|
});
|
|
14832
|
+
describe('#refreshDataChannelToken()', () => {
|
|
14833
|
+
let meeting;
|
|
14834
|
+
|
|
14835
|
+
beforeEach(() => {
|
|
14836
|
+
meeting = Object.create(Meeting.prototype);
|
|
14837
|
+
meeting.locusUrl = 'https://locus.example.com';
|
|
14838
|
+
meeting.meetingRequest = {
|
|
14839
|
+
fetchDatachannelToken: sinon.stub().resolves({
|
|
14840
|
+
body: {datachannelToken: 'mock-token'},
|
|
14841
|
+
}),
|
|
14842
|
+
};
|
|
14843
|
+
meeting.members = {
|
|
14844
|
+
selfId: 'self-123',
|
|
14845
|
+
};
|
|
14846
|
+
meeting.webinar = {
|
|
14847
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
|
|
14848
|
+
};
|
|
14849
|
+
});
|
|
14850
|
+
|
|
14851
|
+
it('calls fetchDatachannelToken with correct parameters', async () => {
|
|
14852
|
+
await meeting.refreshDataChannelToken();
|
|
14853
|
+
|
|
14854
|
+
sinon.assert.calledOnce(meeting.meetingRequest.fetchDatachannelToken);
|
|
14855
|
+
|
|
14856
|
+
sinon.assert.calledWith(meeting.meetingRequest.fetchDatachannelToken, {
|
|
14857
|
+
locusUrl: 'https://locus.example.com',
|
|
14858
|
+
requestingParticipantId: 'self-123',
|
|
14859
|
+
isPracticeSession: true,
|
|
14860
|
+
});
|
|
14861
|
+
});
|
|
14862
|
+
|
|
14863
|
+
it('returns the correct structured result', async () => {
|
|
14864
|
+
const result = await meeting.refreshDataChannelToken();
|
|
14865
|
+
|
|
14866
|
+
expect(result).to.deep.equal({
|
|
14867
|
+
body: {
|
|
14868
|
+
datachannelToken: 'mock-token',
|
|
14869
|
+
dataChannelTokenType: 'llm-practice-session',
|
|
14870
|
+
},
|
|
14871
|
+
});
|
|
14872
|
+
});
|
|
14873
|
+
});
|
|
14874
|
+
describe('#getDataChannelTokenType', () => {
|
|
14875
|
+
it('returns PracticeSession when webinar is in practice session mode', () => {
|
|
14876
|
+
meeting.webinar = {
|
|
14877
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(true),
|
|
14878
|
+
};
|
|
14879
|
+
|
|
14880
|
+
const result = meeting.getDataChannelTokenType();
|
|
14881
|
+
|
|
14882
|
+
expect(result).to.equal('llm-practice-session');
|
|
14883
|
+
});
|
|
14884
|
+
|
|
14885
|
+
it('returns Default when not in practice session mode', () => {
|
|
14886
|
+
meeting.webinar = {
|
|
14887
|
+
isJoinPracticeSessionDataChannel: sinon.stub().returns(false),
|
|
14888
|
+
};
|
|
14889
|
+
|
|
14890
|
+
const result = meeting.getDataChannelTokenType();
|
|
14891
|
+
|
|
14892
|
+
expect(result).to.equal('llm-default-session');
|
|
14893
|
+
});
|
|
14894
|
+
});
|
|
14187
14895
|
describe('#stopKeepAlive', () => {
|
|
14188
14896
|
let clock;
|
|
14189
14897
|
const defaultKeepAliveUrl = 'keep.alive.url';
|