@webex/plugin-meetings 3.7.0-next.6 → 3.7.0-next.60
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/annotation/index.js +17 -0
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/index.js +1 -1
- package/dist/common/errors/join-forbidden-error.js +52 -0
- package/dist/common/errors/join-forbidden-error.js.map +1 -0
- package/dist/common/errors/{webinar-registration-error.js → join-webinar-error.js} +12 -12
- package/dist/common/errors/join-webinar-error.js.map +1 -0
- package/dist/common/errors/multistream-not-supported-error.js +53 -0
- package/dist/common/errors/multistream-not-supported-error.js.map +1 -0
- package/dist/config.js +1 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.js +46 -5
- package/dist/constants.js.map +1 -1
- package/dist/index.js +16 -11
- package/dist/index.js.map +1 -1
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/index.js +14 -3
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/selfUtils.js +35 -17
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/meeting/brbState.js +167 -0
- package/dist/meeting/brbState.js.map +1 -0
- package/dist/meeting/in-meeting-actions.js +2 -0
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +774 -649
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +9 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +1 -6
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +30 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +16 -16
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +96 -33
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +1 -1
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/index.js +107 -55
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +2 -0
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +39 -28
- package/dist/member/util.js.map +1 -1
- package/dist/metrics/constants.js +3 -2
- package/dist/metrics/constants.js.map +1 -1
- package/dist/multistream/remoteMedia.js +30 -15
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +24 -0
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/reachability/index.js +31 -3
- package/dist/reachability/index.js.map +1 -1
- package/dist/roap/index.js +10 -8
- package/dist/roap/index.js.map +1 -1
- package/dist/types/annotation/index.d.ts +5 -0
- package/dist/types/common/errors/join-forbidden-error.d.ts +15 -0
- package/dist/types/common/errors/{webinar-registration-error.d.ts → join-webinar-error.d.ts} +2 -2
- package/dist/types/common/errors/multistream-not-supported-error.d.ts +17 -0
- package/dist/types/constants.d.ts +38 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/locus-info/index.d.ts +2 -1
- package/dist/types/meeting/brbState.d.ts +54 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
- package/dist/types/meeting/index.d.ts +21 -12
- package/dist/types/meeting/locusMediaRequest.d.ts +4 -0
- package/dist/types/meeting/request.d.ts +12 -1
- package/dist/types/meeting/request.type.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +1 -1
- package/dist/types/meeting-info/meeting-info-v2.d.ts +27 -4
- package/dist/types/meetings/index.d.ts +19 -1
- package/dist/types/meetings/meetings.types.d.ts +8 -0
- package/dist/types/member/index.d.ts +1 -0
- package/dist/types/member/types.d.ts +7 -0
- package/dist/types/metrics/constants.d.ts +2 -1
- package/dist/types/multistream/sendSlotManager.d.ts +8 -1
- package/dist/types/reachability/index.d.ts +9 -1
- package/dist/webinar/index.js +354 -3
- package/dist/webinar/index.js.map +1 -1
- package/package.json +23 -22
- package/src/annotation/index.ts +16 -0
- package/src/common/errors/join-forbidden-error.ts +26 -0
- package/src/common/errors/join-webinar-error.ts +24 -0
- package/src/common/errors/multistream-not-supported-error.ts +30 -0
- package/src/config.ts +1 -1
- package/src/constants.ts +43 -3
- package/src/index.ts +5 -3
- package/src/locus-info/index.ts +20 -3
- package/src/locus-info/selfUtils.ts +24 -6
- package/src/meeting/brbState.ts +169 -0
- package/src/meeting/in-meeting-actions.ts +4 -0
- package/src/meeting/index.ts +256 -82
- package/src/meeting/locusMediaRequest.ts +7 -0
- package/src/meeting/muteState.ts +1 -6
- package/src/meeting/request.ts +26 -1
- package/src/meeting/request.type.ts +7 -0
- package/src/meeting/util.ts +8 -10
- package/src/meeting-info/meeting-info-v2.ts +74 -11
- package/src/meeting-info/utilv2.ts +3 -1
- package/src/meetings/index.ts +79 -20
- package/src/meetings/meetings.types.ts +10 -0
- package/src/meetings/util.ts +2 -1
- package/src/member/index.ts +9 -0
- package/src/member/types.ts +8 -0
- package/src/member/util.ts +34 -24
- package/src/metrics/constants.ts +2 -1
- package/src/multistream/remoteMedia.ts +28 -15
- package/src/multistream/sendSlotManager.ts +31 -0
- package/src/reachability/index.ts +29 -1
- package/src/roap/index.ts +10 -8
- package/src/webinar/index.ts +197 -3
- package/test/unit/spec/annotation/index.ts +46 -1
- package/test/unit/spec/locus-info/index.js +292 -60
- package/test/unit/spec/locus-info/selfConstant.js +7 -0
- package/test/unit/spec/locus-info/selfUtils.js +101 -1
- package/test/unit/spec/meeting/brbState.ts +114 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
- package/test/unit/spec/meeting/index.js +733 -106
- package/test/unit/spec/meeting/muteState.js +0 -24
- package/test/unit/spec/meeting/utils.js +22 -19
- package/test/unit/spec/meeting-info/meetinginfov2.js +46 -4
- package/test/unit/spec/meeting-info/utilv2.js +17 -0
- package/test/unit/spec/meetings/index.js +159 -18
- package/test/unit/spec/meetings/utils.js +10 -0
- package/test/unit/spec/member/util.js +52 -11
- package/test/unit/spec/multistream/remoteMedia.ts +11 -7
- package/test/unit/spec/reachability/index.ts +120 -10
- package/test/unit/spec/roap/index.ts +47 -0
- package/test/unit/spec/webinar/index.ts +457 -0
- package/dist/common/errors/webinar-registration-error.js.map +0 -1
- package/src/common/errors/webinar-registration-error.ts +0 -27
|
@@ -91,14 +91,15 @@ import ParameterError from '../../../../src/common/errors/parameter';
|
|
|
91
91
|
import PasswordError from '../../../../src/common/errors/password-error';
|
|
92
92
|
import CaptchaError from '../../../../src/common/errors/captcha-error';
|
|
93
93
|
import PermissionError from '../../../../src/common/errors/permission';
|
|
94
|
-
import
|
|
94
|
+
import JoinWebinarError from '../../../../src/common/errors/join-webinar-error';
|
|
95
95
|
import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
|
|
96
|
+
import MultistreamNotSupportedError from '../../../../src/common/errors/multistream-not-supported-error';;
|
|
96
97
|
import testUtils from '../../../utils/testUtils';
|
|
97
98
|
import {
|
|
98
99
|
MeetingInfoV2CaptchaError,
|
|
99
100
|
MeetingInfoV2PasswordError,
|
|
100
101
|
MeetingInfoV2PolicyError,
|
|
101
|
-
|
|
102
|
+
MeetingInfoV2JoinWebinarError, MeetingInfoV2JoinForbiddenError,
|
|
102
103
|
} from '../../../../src/meeting-info/meeting-info-v2';
|
|
103
104
|
import {
|
|
104
105
|
DTLS_HANDSHAKE_FAILED_CLIENT_CODE,
|
|
@@ -113,6 +114,8 @@ import {ERROR_DESCRIPTIONS} from '@webex/internal-plugin-metrics/src/call-diagno
|
|
|
113
114
|
import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
|
|
114
115
|
|
|
115
116
|
import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
|
|
117
|
+
import { createBrbState } from '@webex/plugin-meetings/src/meeting/brbState';
|
|
118
|
+
import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
|
|
116
119
|
|
|
117
120
|
describe('plugin-meetings', () => {
|
|
118
121
|
const logger = {
|
|
@@ -244,6 +247,7 @@ describe('plugin-meetings', () => {
|
|
|
244
247
|
isAnyPublicClusterReachable: sinon.stub().resolves(true),
|
|
245
248
|
getReachabilityResults: sinon.stub().resolves(undefined),
|
|
246
249
|
getReachabilityMetrics: sinon.stub().resolves({}),
|
|
250
|
+
stopReachability: sinon.stub(),
|
|
247
251
|
};
|
|
248
252
|
webex.internal.llm.on = sinon.stub();
|
|
249
253
|
webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
|
|
@@ -652,7 +656,7 @@ describe('plugin-meetings', () => {
|
|
|
652
656
|
const fakeTurnServerInfo = {id: 'fake turn info'};
|
|
653
657
|
const fakeJoinResult = {id: 'join result'};
|
|
654
658
|
|
|
655
|
-
const joinOptions = {correlationId: '12345'};
|
|
659
|
+
const joinOptions = {correlationId: '12345', enableMultistream: true};
|
|
656
660
|
const mediaOptions = {audioEnabled: true, allowMediaInLobby: true};
|
|
657
661
|
|
|
658
662
|
let generateTurnDiscoveryRequestMessageStub;
|
|
@@ -661,7 +665,10 @@ describe('plugin-meetings', () => {
|
|
|
661
665
|
let addMediaInternalStub;
|
|
662
666
|
|
|
663
667
|
beforeEach(() => {
|
|
664
|
-
meeting.join = sinon.stub().
|
|
668
|
+
meeting.join = sinon.stub().callsFake((joinOptions) => {
|
|
669
|
+
meeting.isMultistream = joinOptions.enableMultistream;
|
|
670
|
+
return Promise.resolve(fakeJoinResult)
|
|
671
|
+
});
|
|
665
672
|
addMediaInternalStub = sinon
|
|
666
673
|
.stub(meeting, 'addMediaInternal')
|
|
667
674
|
.returns(Promise.resolve(test4));
|
|
@@ -700,7 +707,7 @@ describe('plugin-meetings', () => {
|
|
|
700
707
|
mediaOptions
|
|
701
708
|
);
|
|
702
709
|
|
|
703
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
710
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
704
711
|
|
|
705
712
|
// resets joinWithMediaRetryInfo
|
|
706
713
|
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
@@ -733,7 +740,7 @@ describe('plugin-meetings', () => {
|
|
|
733
740
|
mediaOptions
|
|
734
741
|
);
|
|
735
742
|
|
|
736
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
743
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
737
744
|
assert.equal(meeting.turnServerUsed, false);
|
|
738
745
|
});
|
|
739
746
|
|
|
@@ -768,7 +775,7 @@ describe('plugin-meetings', () => {
|
|
|
768
775
|
mediaOptions
|
|
769
776
|
);
|
|
770
777
|
|
|
771
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
778
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
772
779
|
});
|
|
773
780
|
|
|
774
781
|
it('should reject if join() fails', async () => {
|
|
@@ -855,7 +862,8 @@ describe('plugin-meetings', () => {
|
|
|
855
862
|
}
|
|
856
863
|
);
|
|
857
864
|
|
|
858
|
-
|
|
865
|
+
// expect multistreamEnabled: false, because this test overrides the join meeting.join stub so it doesn't set the isMultistream flag
|
|
866
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: false});
|
|
859
867
|
|
|
860
868
|
// resets joinWithMediaRetryInfo
|
|
861
869
|
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
|
@@ -944,7 +952,7 @@ describe('plugin-meetings', () => {
|
|
|
944
952
|
mediaOptions,
|
|
945
953
|
});
|
|
946
954
|
|
|
947
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
|
955
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
|
948
956
|
|
|
949
957
|
assert.calledOnce(meeting.join);
|
|
950
958
|
assert.notCalled(leaveStub);
|
|
@@ -1038,6 +1046,7 @@ describe('plugin-meetings', () => {
|
|
|
1038
1046
|
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1039
1047
|
initiateOffer: sinon.stub().resolves({}),
|
|
1040
1048
|
on: sinon.stub(),
|
|
1049
|
+
createSendSlot: sinon.stub(),
|
|
1041
1050
|
};
|
|
1042
1051
|
|
|
1043
1052
|
/* Setup the stubs so that the first call to addMediaInternal() fails
|
|
@@ -1054,12 +1063,14 @@ describe('plugin-meetings', () => {
|
|
|
1054
1063
|
|
|
1055
1064
|
sinon.stub(meeting.roap, 'doTurnDiscovery').resolves({turnServerInfo: 'fake turn info'});
|
|
1056
1065
|
|
|
1066
|
+
// calling joinWithMedia() with enableMultistream=false, because this test uses real addMediaInternal() implementation
|
|
1067
|
+
// and it requires less stubs when it's without multistream
|
|
1057
1068
|
const result = await meeting.joinWithMedia({
|
|
1058
|
-
joinOptions,
|
|
1069
|
+
joinOptions: {...joinOptions, enableMultistream: false},
|
|
1059
1070
|
mediaOptions,
|
|
1060
1071
|
});
|
|
1061
1072
|
|
|
1062
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: undefined});
|
|
1073
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: undefined, multistreamEnabled: false});
|
|
1063
1074
|
|
|
1064
1075
|
assert.calledOnce(meeting.join);
|
|
1065
1076
|
|
|
@@ -1134,6 +1145,7 @@ describe('plugin-meetings', () => {
|
|
|
1134
1145
|
addMediaError.name = 'SdpOfferCreationError';
|
|
1135
1146
|
|
|
1136
1147
|
meeting.addMediaInternal.rejects(addMediaError);
|
|
1148
|
+
sinon.stub(meeting, 'leave').resolves();
|
|
1137
1149
|
|
|
1138
1150
|
await assert.isRejected(
|
|
1139
1151
|
meeting.joinWithMedia({
|
|
@@ -1162,6 +1174,7 @@ describe('plugin-meetings', () => {
|
|
|
1162
1174
|
type: addMediaError.name,
|
|
1163
1175
|
}
|
|
1164
1176
|
);
|
|
1177
|
+
assert.calledOnceWithExactly(meeting.leave, {resourceId: undefined, reason: 'joinWithMedia failure'})
|
|
1165
1178
|
});
|
|
1166
1179
|
});
|
|
1167
1180
|
|
|
@@ -1238,6 +1251,7 @@ describe('plugin-meetings', () => {
|
|
|
1238
1251
|
webex.internal.voicea.off = sinon.stub();
|
|
1239
1252
|
webex.internal.voicea.listenToEvents = sinon.stub();
|
|
1240
1253
|
webex.internal.voicea.turnOnCaptions = sinon.stub();
|
|
1254
|
+
webex.internal.voicea.deregisterEvents = sinon.stub();
|
|
1241
1255
|
});
|
|
1242
1256
|
|
|
1243
1257
|
it('should stop listening to voicea events and also trigger a stop event', () => {
|
|
@@ -1566,6 +1580,55 @@ describe('plugin-meetings', () => {
|
|
|
1566
1580
|
fakeProcessedReaction
|
|
1567
1581
|
);
|
|
1568
1582
|
});
|
|
1583
|
+
|
|
1584
|
+
it('should fail quietly if participantId does not exist in membersCollection', () => {
|
|
1585
|
+
LoggerProxy.logger.warn = sinon.stub();
|
|
1586
|
+
meeting.isReactionsSupported = sinon.stub().returns(true);
|
|
1587
|
+
meeting.config.receiveReactions = true;
|
|
1588
|
+
const fakeSendersName = 'Fake reactors name';
|
|
1589
|
+
const fakeReactionPayload = {
|
|
1590
|
+
type: 'fake_type',
|
|
1591
|
+
codepoints: 'fake_codepoints',
|
|
1592
|
+
shortcodes: 'fake_shortcodes',
|
|
1593
|
+
tone: {
|
|
1594
|
+
type: 'fake_tone_type',
|
|
1595
|
+
codepoints: 'fake_tone_codepoints',
|
|
1596
|
+
shortcodes: 'fake_tone_shortcodes',
|
|
1597
|
+
},
|
|
1598
|
+
};
|
|
1599
|
+
const fakeSenderPayload = {
|
|
1600
|
+
participantId: 'fake_participant_id',
|
|
1601
|
+
};
|
|
1602
|
+
const fakeProcessedReaction = {
|
|
1603
|
+
reaction: fakeReactionPayload,
|
|
1604
|
+
sender: {
|
|
1605
|
+
id: fakeSenderPayload.participantId,
|
|
1606
|
+
name: fakeSendersName,
|
|
1607
|
+
},
|
|
1608
|
+
};
|
|
1609
|
+
const fakeRelayEvent = {
|
|
1610
|
+
data: {
|
|
1611
|
+
relayType: REACTION_RELAY_TYPES.REACTION,
|
|
1612
|
+
reaction: fakeReactionPayload,
|
|
1613
|
+
sender: fakeSenderPayload,
|
|
1614
|
+
},
|
|
1615
|
+
};
|
|
1616
|
+
meeting.processRelayEvent(fakeRelayEvent);
|
|
1617
|
+
assert.calledWith(
|
|
1618
|
+
LoggerProxy.logger.warn,
|
|
1619
|
+
`Meeting:index#processRelayEvent --> Skipping handling of react for ${meeting.id}. participantId fake_participant_id does not exist in membersCollection.`
|
|
1620
|
+
);
|
|
1621
|
+
assert.neverCalledWith(
|
|
1622
|
+
TriggerProxy.trigger,
|
|
1623
|
+
sinon.match.instanceOf(Meeting),
|
|
1624
|
+
{
|
|
1625
|
+
file: 'meeting/index',
|
|
1626
|
+
function: 'join',
|
|
1627
|
+
},
|
|
1628
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
1629
|
+
fakeProcessedReaction
|
|
1630
|
+
);
|
|
1631
|
+
});
|
|
1569
1632
|
});
|
|
1570
1633
|
|
|
1571
1634
|
describe('#handleLLMOnline', () => {
|
|
@@ -1705,6 +1768,12 @@ describe('plugin-meetings', () => {
|
|
|
1705
1768
|
sinon.assert.called(setCorrelationIdSpy);
|
|
1706
1769
|
assert.equal(meeting.correlationId, '123');
|
|
1707
1770
|
});
|
|
1771
|
+
|
|
1772
|
+
it('should not send client.call.initiated if told not to', async () => {
|
|
1773
|
+
await meeting.join({sendCallInitiated: false});
|
|
1774
|
+
|
|
1775
|
+
sinon.assert.notCalled(webex.internal.newMetrics.submitClientEvent);
|
|
1776
|
+
});
|
|
1708
1777
|
});
|
|
1709
1778
|
|
|
1710
1779
|
describe('failure', () => {
|
|
@@ -2028,6 +2097,7 @@ describe('plugin-meetings', () => {
|
|
|
2028
2097
|
someReachabilityMetric1: 'some value1',
|
|
2029
2098
|
someReachabilityMetric2: 'some value2',
|
|
2030
2099
|
}),
|
|
2100
|
+
stopReachability: sinon.stub(),
|
|
2031
2101
|
};
|
|
2032
2102
|
|
|
2033
2103
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
|
@@ -2447,6 +2517,7 @@ describe('plugin-meetings', () => {
|
|
|
2447
2517
|
assert.calledOnce(meeting.setMercuryListener);
|
|
2448
2518
|
assert.calledOnce(fakeMediaConnection.initiateOffer);
|
|
2449
2519
|
assert.equal(meeting.allowMediaInLobby, allowMediaInLobby);
|
|
2520
|
+
assert.calledOnce(webex.meetings.reachability.stopReachability);
|
|
2450
2521
|
};
|
|
2451
2522
|
|
|
2452
2523
|
it('should attach the media and return promise', async () => {
|
|
@@ -2492,9 +2563,11 @@ describe('plugin-meetings', () => {
|
|
|
2492
2563
|
mediaSettings: {},
|
|
2493
2564
|
});
|
|
2494
2565
|
|
|
2495
|
-
const checkLogCounter = (
|
|
2566
|
+
const checkLogCounter = (delayInMinutes, expectedCounter) => {
|
|
2567
|
+
const delayInMilliseconds = delayInMinutes * 60 * 1000;
|
|
2568
|
+
|
|
2496
2569
|
// first check that the counter is not increased just before the delay
|
|
2497
|
-
clock.tick(
|
|
2570
|
+
clock.tick(delayInMilliseconds - 50);
|
|
2498
2571
|
assert.equal(logUploadCounter, expectedCounter - 1);
|
|
2499
2572
|
|
|
2500
2573
|
// and now check that it has reached expected value after the delay
|
|
@@ -2502,22 +2575,18 @@ describe('plugin-meetings', () => {
|
|
|
2502
2575
|
assert.equal(logUploadCounter, expectedCounter);
|
|
2503
2576
|
};
|
|
2504
2577
|
|
|
2505
|
-
checkLogCounter(
|
|
2506
|
-
checkLogCounter(
|
|
2507
|
-
checkLogCounter(
|
|
2508
|
-
checkLogCounter(
|
|
2509
|
-
checkLogCounter(
|
|
2510
|
-
checkLogCounter(30000, 6);
|
|
2511
|
-
checkLogCounter(30000, 7);
|
|
2512
|
-
checkLogCounter(60000, 8);
|
|
2513
|
-
checkLogCounter(60000, 9);
|
|
2514
|
-
checkLogCounter(60000, 10);
|
|
2578
|
+
checkLogCounter(0.1, 1);
|
|
2579
|
+
checkLogCounter(15, 2);
|
|
2580
|
+
checkLogCounter(30, 3);
|
|
2581
|
+
checkLogCounter(60, 4);
|
|
2582
|
+
checkLogCounter(60, 5);
|
|
2515
2583
|
|
|
2516
|
-
// simulate media connection being removed ->
|
|
2584
|
+
// simulate media connection being removed -> 1 more upload should happen, but nothing more afterwards
|
|
2517
2585
|
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
2586
|
+
checkLogCounter(60, 6);
|
|
2518
2587
|
|
|
2519
|
-
clock.tick(
|
|
2520
|
-
assert.equal(logUploadCounter,
|
|
2588
|
+
clock.tick(120 * 1000 * 60);
|
|
2589
|
+
assert.equal(logUploadCounter, 6);
|
|
2521
2590
|
|
|
2522
2591
|
clock.restore();
|
|
2523
2592
|
});
|
|
@@ -2644,6 +2713,7 @@ describe('plugin-meetings', () => {
|
|
|
2644
2713
|
webex.meetings.reachability = {
|
|
2645
2714
|
isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
|
|
2646
2715
|
getReachabilityMetrics: sinon.stub().resolves(),
|
|
2716
|
+
stopReachability: sinon.stub(),
|
|
2647
2717
|
};
|
|
2648
2718
|
const MOCK_CLIENT_ERROR_CODE = 2004;
|
|
2649
2719
|
const generateClientErrorCodeForIceFailureStub = sinon
|
|
@@ -2852,6 +2922,7 @@ describe('plugin-meetings', () => {
|
|
|
2852
2922
|
.onCall(2)
|
|
2853
2923
|
.resolves(false),
|
|
2854
2924
|
getReachabilityMetrics: sinon.stub().resolves({}),
|
|
2925
|
+
stopReachability: sinon.stub(),
|
|
2855
2926
|
};
|
|
2856
2927
|
const getErrorPayloadForClientErrorCodeStub =
|
|
2857
2928
|
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
@@ -3146,6 +3217,7 @@ describe('plugin-meetings', () => {
|
|
|
3146
3217
|
someReachabilityMetric1: 'some value1',
|
|
3147
3218
|
someReachabilityMetric2: 'some value2',
|
|
3148
3219
|
}),
|
|
3220
|
+
stopReachability: sinon.stub(),
|
|
3149
3221
|
};
|
|
3150
3222
|
meeting.iceCandidatesCount = 3;
|
|
3151
3223
|
meeting.iceCandidateErrors.set('701_error', 3);
|
|
@@ -3475,6 +3547,51 @@ describe('plugin-meetings', () => {
|
|
|
3475
3547
|
});
|
|
3476
3548
|
});
|
|
3477
3549
|
|
|
3550
|
+
it('counts the number of members that are in the meeting for MEDIA_QUALITY event', async () => {
|
|
3551
|
+
let fakeMembersCollection = {
|
|
3552
|
+
members: {
|
|
3553
|
+
member1: { isInMeeting: true },
|
|
3554
|
+
member2: { isInMeeting: true },
|
|
3555
|
+
member3: { isInMeeting: false },
|
|
3556
|
+
},
|
|
3557
|
+
};
|
|
3558
|
+
sinon.stub(meeting, 'getMembers').returns({ membersCollection: fakeMembersCollection });
|
|
3559
|
+
const fakeData = { intervalMetadata: {}, networkType: 'wifi' };
|
|
3560
|
+
|
|
3561
|
+
statsAnalyzerStub.emit(
|
|
3562
|
+
{ file: 'test', function: 'test' },
|
|
3563
|
+
StatsAnalyzerEventNames.MEDIA_QUALITY,
|
|
3564
|
+
{ data: fakeData }
|
|
3565
|
+
);
|
|
3566
|
+
|
|
3567
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
|
|
3568
|
+
name: 'client.mediaquality.event',
|
|
3569
|
+
options: {
|
|
3570
|
+
meetingId: meeting.id,
|
|
3571
|
+
},
|
|
3572
|
+
payload: {
|
|
3573
|
+
intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 2))],
|
|
3574
|
+
},
|
|
3575
|
+
});
|
|
3576
|
+
fakeMembersCollection.members.member2.isInMeeting = false;
|
|
3577
|
+
|
|
3578
|
+
statsAnalyzerStub.emit(
|
|
3579
|
+
{ file: 'test', function: 'test' },
|
|
3580
|
+
StatsAnalyzerEventNames.MEDIA_QUALITY,
|
|
3581
|
+
{ data: fakeData }
|
|
3582
|
+
);
|
|
3583
|
+
|
|
3584
|
+
assert.calledWithMatch(webex.internal.newMetrics.submitMQE, {
|
|
3585
|
+
name: 'client.mediaquality.event',
|
|
3586
|
+
options: {
|
|
3587
|
+
meetingId: meeting.id,
|
|
3588
|
+
},
|
|
3589
|
+
payload: {
|
|
3590
|
+
intervals: [sinon.match.has('intervalMetadata', sinon.match.has('meetingUserCount', 1))],
|
|
3591
|
+
},
|
|
3592
|
+
});
|
|
3593
|
+
});
|
|
3594
|
+
|
|
3478
3595
|
it('calls submitMQE correctly', async () => {
|
|
3479
3596
|
const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
|
|
3480
3597
|
|
|
@@ -3552,14 +3669,6 @@ describe('plugin-meetings', () => {
|
|
|
3552
3669
|
});
|
|
3553
3670
|
});
|
|
3554
3671
|
|
|
3555
|
-
it('succeeds even if getDevices() throws', async () => {
|
|
3556
|
-
meeting.meetingState = 'ACTIVE';
|
|
3557
|
-
|
|
3558
|
-
sinon.stub(InternalMediaCoreModule, 'getDevices').rejects(new Error('fake error'));
|
|
3559
|
-
|
|
3560
|
-
await meeting.addMedia();
|
|
3561
|
-
});
|
|
3562
|
-
|
|
3563
3672
|
describe('CA ice failures checks', () => {
|
|
3564
3673
|
[
|
|
3565
3674
|
{
|
|
@@ -3613,6 +3722,7 @@ describe('plugin-meetings', () => {
|
|
|
3613
3722
|
|
|
3614
3723
|
webex.meetings.reachability = {
|
|
3615
3724
|
isWebexMediaBackendUnreachable: sinon.stub().resolves(unreachable || false),
|
|
3725
|
+
stopReachability: sinon.stub(),
|
|
3616
3726
|
};
|
|
3617
3727
|
|
|
3618
3728
|
const generateClientErrorCodeForIceFailureStub = sinon
|
|
@@ -3701,6 +3811,73 @@ describe('plugin-meetings', () => {
|
|
|
3701
3811
|
});
|
|
3702
3812
|
});
|
|
3703
3813
|
|
|
3814
|
+
describe(`#beRightBack`, () => {
|
|
3815
|
+
const fakeMultistreamRoapMediaConnection = {
|
|
3816
|
+
createSendSlot: sinon.stub().returns({
|
|
3817
|
+
setSourceStateOverride: sinon.stub().resolves(),
|
|
3818
|
+
clearSourceStateOverride: sinon.stub().resolves(),
|
|
3819
|
+
}),
|
|
3820
|
+
};
|
|
3821
|
+
|
|
3822
|
+
beforeEach(() => {
|
|
3823
|
+
meeting.mediaProperties.webrtcMediaConnection = {createSendSlot: sinon.stub()};
|
|
3824
|
+
meeting.sendSlotManager.createSlot(
|
|
3825
|
+
fakeMultistreamRoapMediaConnection,
|
|
3826
|
+
MediaType.VideoMain
|
|
3827
|
+
);
|
|
3828
|
+
|
|
3829
|
+
meeting.locusUrl = 'locus url';
|
|
3830
|
+
meeting.deviceUrl = 'device url';
|
|
3831
|
+
meeting.selfId = 'self id';
|
|
3832
|
+
meeting.brbState = createBrbState(meeting, false);
|
|
3833
|
+
meeting.brbState.enable = sinon.stub().resolves();
|
|
3834
|
+
});
|
|
3835
|
+
|
|
3836
|
+
afterEach(() => {
|
|
3837
|
+
sinon.restore();
|
|
3838
|
+
});
|
|
3839
|
+
|
|
3840
|
+
it('should have #beRightBack', () => {
|
|
3841
|
+
assert.exists(meeting.beRightBack);
|
|
3842
|
+
});
|
|
3843
|
+
|
|
3844
|
+
describe('when in a multistream meeting', () => {
|
|
3845
|
+
|
|
3846
|
+
beforeEach(() => {
|
|
3847
|
+
meeting.isMultistream = true;
|
|
3848
|
+
});
|
|
3849
|
+
|
|
3850
|
+
it('should enable #beRightBack and return a promise', async () => {
|
|
3851
|
+
const brbResult = meeting.beRightBack(true);
|
|
3852
|
+
|
|
3853
|
+
await brbResult;
|
|
3854
|
+
assert.exists(brbResult.then);
|
|
3855
|
+
assert.calledOnce(meeting.brbState.enable);
|
|
3856
|
+
})
|
|
3857
|
+
|
|
3858
|
+
it('should disable #beRightBack and return a promise', async () => {
|
|
3859
|
+
const brbResult = meeting.beRightBack(false);
|
|
3860
|
+
|
|
3861
|
+
await brbResult;
|
|
3862
|
+
assert.exists(brbResult.then);
|
|
3863
|
+
assert.calledOnce(meeting.brbState.enable);
|
|
3864
|
+
})
|
|
3865
|
+
|
|
3866
|
+
it('should throw an error and reject the promise if setBrb fails', async () => {
|
|
3867
|
+
const error = new Error('setBrb failed');
|
|
3868
|
+
meeting.brbState.enable.rejects(error);
|
|
3869
|
+
|
|
3870
|
+
try {
|
|
3871
|
+
await meeting.beRightBack(true);
|
|
3872
|
+
} catch (err) {
|
|
3873
|
+
assert.instanceOf(err, Error);
|
|
3874
|
+
assert.equal(err.message, 'setBrb failed');
|
|
3875
|
+
assert.isRejected((Promise.reject()));
|
|
3876
|
+
}
|
|
3877
|
+
})
|
|
3878
|
+
});
|
|
3879
|
+
});
|
|
3880
|
+
|
|
3704
3881
|
/* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
|
|
3705
3882
|
They mock the @webex/internal-media-core and sending of /media http requests to Locus.
|
|
3706
3883
|
Their main purpose is to test that we send the right http requests to Locus and make right calls
|
|
@@ -3743,8 +3920,12 @@ describe('plugin-meetings', () => {
|
|
|
3743
3920
|
meeting.setMercuryListener = sinon.stub();
|
|
3744
3921
|
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
3745
3922
|
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
|
3746
|
-
meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon
|
|
3747
|
-
|
|
3923
|
+
meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap = sinon
|
|
3924
|
+
.stub()
|
|
3925
|
+
.resolves({id: 'fake reachability'});
|
|
3926
|
+
meeting.webex.meetings.reachability.getClientMediaPreferences = sinon
|
|
3927
|
+
.stub()
|
|
3928
|
+
.resolves({id: 'fake clientMediaPreferences'});
|
|
3748
3929
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
|
3749
3930
|
turnServerInfo: {
|
|
3750
3931
|
url: 'turns:turn-server-url:443?transport=tcp',
|
|
@@ -3825,6 +4006,7 @@ describe('plugin-meetings', () => {
|
|
|
3825
4006
|
initiateOffer: sinon.stub().resolves({}),
|
|
3826
4007
|
update: sinon.stub().resolves({}),
|
|
3827
4008
|
on: sinon.stub(),
|
|
4009
|
+
roapMessageReceived: sinon.stub()
|
|
3828
4010
|
};
|
|
3829
4011
|
|
|
3830
4012
|
fakeMultistreamRoapMediaConnection = {
|
|
@@ -3911,8 +4093,10 @@ describe('plugin-meetings', () => {
|
|
|
3911
4093
|
};
|
|
3912
4094
|
|
|
3913
4095
|
// simulates a Roap offer being generated by the RoapMediaConnection
|
|
3914
|
-
const simulateRoapOffer = async () => {
|
|
3915
|
-
|
|
4096
|
+
const simulateRoapOffer = async (stubWaitingForAnswer = true) => {
|
|
4097
|
+
if (stubWaitingForAnswer) {
|
|
4098
|
+
meeting.deferSDPAnswer = {resolve: sinon.stub()};
|
|
4099
|
+
}
|
|
3916
4100
|
const roapListener = getRoapListener();
|
|
3917
4101
|
|
|
3918
4102
|
await roapListener({roapMessage: roapOfferMessage});
|
|
@@ -3930,8 +4114,14 @@ describe('plugin-meetings', () => {
|
|
|
3930
4114
|
const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
|
|
3931
4115
|
const {sdp, seq, tieBreaker} = roapOfferMessage;
|
|
3932
4116
|
|
|
3933
|
-
assert.calledWith(
|
|
3934
|
-
|
|
4117
|
+
assert.calledWith(
|
|
4118
|
+
meeting.webex.meetings.reachability.getClientMediaPreferences,
|
|
4119
|
+
meeting.isMultistream,
|
|
4120
|
+
0
|
|
4121
|
+
);
|
|
4122
|
+
assert.calledWith(
|
|
4123
|
+
meeting.webex.meetings.reachability.getReachabilityReportToAttachToRoap
|
|
4124
|
+
);
|
|
3935
4125
|
|
|
3936
4126
|
assert.calledWith(locusMediaRequestStub, {
|
|
3937
4127
|
method: 'PUT',
|
|
@@ -4015,8 +4205,9 @@ describe('plugin-meetings', () => {
|
|
|
4015
4205
|
remoteQualityLevel,
|
|
4016
4206
|
expectedDebugId,
|
|
4017
4207
|
meetingId,
|
|
4208
|
+
expectMultistream = isMultistream,
|
|
4018
4209
|
}) => {
|
|
4019
|
-
if (
|
|
4210
|
+
if (expectMultistream) {
|
|
4020
4211
|
const {iceServers} = mediaConnectionConfig;
|
|
4021
4212
|
|
|
4022
4213
|
assert.calledOnceWithMatch(
|
|
@@ -4176,7 +4367,6 @@ describe('plugin-meetings', () => {
|
|
|
4176
4367
|
});
|
|
4177
4368
|
|
|
4178
4369
|
it('addMedia() works correctly when media is enabled with streams to publish', async () => {
|
|
4179
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4180
4370
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
4181
4371
|
await simulateRoapOffer();
|
|
4182
4372
|
await simulateRoapOk();
|
|
@@ -4207,12 +4397,9 @@ describe('plugin-meetings', () => {
|
|
|
4207
4397
|
|
|
4208
4398
|
// and that these were the only /media requests that were sent
|
|
4209
4399
|
assert.calledTwice(locusMediaRequestStub);
|
|
4210
|
-
|
|
4211
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4212
4400
|
});
|
|
4213
4401
|
|
|
4214
4402
|
it('addMedia() works correctly when media is enabled with streams to publish and stream is user muted', async () => {
|
|
4215
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4216
4403
|
fakeMicrophoneStream.userMuted = true;
|
|
4217
4404
|
|
|
4218
4405
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
|
@@ -4244,7 +4431,6 @@ describe('plugin-meetings', () => {
|
|
|
4244
4431
|
|
|
4245
4432
|
// and that these were the only /media requests that were sent
|
|
4246
4433
|
assert.calledTwice(locusMediaRequestStub);
|
|
4247
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4248
4434
|
});
|
|
4249
4435
|
|
|
4250
4436
|
it('addMedia() works correctly when media is enabled with tracks to publish and track is ended', async () => {
|
|
@@ -4316,7 +4502,6 @@ describe('plugin-meetings', () => {
|
|
|
4316
4502
|
});
|
|
4317
4503
|
|
|
4318
4504
|
it('addMedia() works correctly when media is disabled with streams to publish', async () => {
|
|
4319
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4320
4505
|
await meeting.addMedia({
|
|
4321
4506
|
localStreams: {microphone: fakeMicrophoneStream},
|
|
4322
4507
|
audioEnabled: false,
|
|
@@ -4350,20 +4535,6 @@ describe('plugin-meetings', () => {
|
|
|
4350
4535
|
|
|
4351
4536
|
// and that these were the only /media requests that were sent
|
|
4352
4537
|
assert.calledTwice(locusMediaRequestStub);
|
|
4353
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
|
4354
|
-
});
|
|
4355
|
-
|
|
4356
|
-
it('handleDeviceLogging not called when media is disabled', async () => {
|
|
4357
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4358
|
-
await meeting.addMedia({
|
|
4359
|
-
localStreams: {microphone: fakeMicrophoneStream},
|
|
4360
|
-
audioEnabled: false,
|
|
4361
|
-
videoEnabled: false,
|
|
4362
|
-
});
|
|
4363
|
-
await simulateRoapOffer();
|
|
4364
|
-
await simulateRoapOk();
|
|
4365
|
-
|
|
4366
|
-
assert.notCalled(handleDeviceLoggingSpy);
|
|
4367
4538
|
});
|
|
4368
4539
|
|
|
4369
4540
|
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
|
@@ -4399,20 +4570,6 @@ describe('plugin-meetings', () => {
|
|
|
4399
4570
|
assert.calledTwice(locusMediaRequestStub);
|
|
4400
4571
|
});
|
|
4401
4572
|
|
|
4402
|
-
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
|
4403
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4404
|
-
await meeting.addMedia({audioEnabled: false});
|
|
4405
|
-
//calling handleDeviceLogging with audioEnaled as true adn videoEnabled as false
|
|
4406
|
-
assert.calledWith(handleDeviceLoggingSpy, false, true);
|
|
4407
|
-
});
|
|
4408
|
-
|
|
4409
|
-
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
|
4410
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4411
|
-
await meeting.addMedia({videoEnabled: false});
|
|
4412
|
-
//calling handleDeviceLogging audioEnabled as true videoEnabled as false
|
|
4413
|
-
assert.calledWith(handleDeviceLoggingSpy, true, false);
|
|
4414
|
-
});
|
|
4415
|
-
|
|
4416
4573
|
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
|
4417
4574
|
await meeting.addMedia({videoEnabled: false});
|
|
4418
4575
|
await simulateRoapOffer();
|
|
@@ -4479,13 +4636,6 @@ describe('plugin-meetings', () => {
|
|
|
4479
4636
|
assert.calledTwice(locusMediaRequestStub);
|
|
4480
4637
|
});
|
|
4481
4638
|
|
|
4482
|
-
it('addMedia() works correctly when both shareAudio and shareVideo is disabled with no streams publish', async () => {
|
|
4483
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
|
4484
|
-
await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false});
|
|
4485
|
-
//calling handleDeviceLogging with audioEnabled true and videoEnabled as true
|
|
4486
|
-
assert.calledWith(handleDeviceLoggingSpy, true, true);
|
|
4487
|
-
});
|
|
4488
|
-
|
|
4489
4639
|
describe('publishStreams()/unpublishStreams() calls', () => {
|
|
4490
4640
|
[
|
|
4491
4641
|
{mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
|
|
@@ -4881,6 +5031,211 @@ describe('plugin-meetings', () => {
|
|
|
4881
5031
|
assert.notCalled(fakeRoapMediaConnection.update);
|
|
4882
5032
|
})
|
|
4883
5033
|
);
|
|
5034
|
+
|
|
5035
|
+
if (isMultistream) {
|
|
5036
|
+
describe('fallback from multistream to transcoded', () => {
|
|
5037
|
+
let multistreamEventListeners;
|
|
5038
|
+
let transcodedEventListeners;
|
|
5039
|
+
let mockStatsAnalyzerCtor;
|
|
5040
|
+
|
|
5041
|
+
const setupFakeRoapMediaConnection = (fakeRoapMediaConnection, eventListeners) => {
|
|
5042
|
+
fakeRoapMediaConnection.on.callsFake((eventName, cb) => {
|
|
5043
|
+
eventListeners[eventName] = cb;
|
|
5044
|
+
});
|
|
5045
|
+
fakeRoapMediaConnection.initiateOffer.callsFake(() => {
|
|
5046
|
+
// simulate offer being generated
|
|
5047
|
+
eventListeners[MediaConnectionEventNames.LOCAL_SDP_OFFER_GENERATED]();
|
|
5048
|
+
|
|
5049
|
+
return Promise.resolve();
|
|
5050
|
+
});
|
|
5051
|
+
};
|
|
5052
|
+
|
|
5053
|
+
beforeEach(() => {
|
|
5054
|
+
multistreamEventListeners = {};
|
|
5055
|
+
transcodedEventListeners = {};
|
|
5056
|
+
|
|
5057
|
+
meeting.config.stats.enableStatsAnalyzer = true;
|
|
5058
|
+
|
|
5059
|
+
setupFakeRoapMediaConnection(fakeRoapMediaConnection, transcodedEventListeners);
|
|
5060
|
+
setupFakeRoapMediaConnection(
|
|
5061
|
+
fakeMultistreamRoapMediaConnection,
|
|
5062
|
+
multistreamEventListeners
|
|
5063
|
+
);
|
|
5064
|
+
|
|
5065
|
+
mockStatsAnalyzerCtor = sinon
|
|
5066
|
+
.stub(InternalMediaCoreModule, 'StatsAnalyzer')
|
|
5067
|
+
.callsFake(() => {
|
|
5068
|
+
return {on: sinon.stub(), stopAnalyzer: sinon.stub()};
|
|
5069
|
+
});
|
|
5070
|
+
|
|
5071
|
+
webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
|
5072
|
+
sinon.stub();
|
|
5073
|
+
|
|
5074
|
+
// setup the mock so that we get an SDP answer not from Homer
|
|
5075
|
+
locusMediaRequestStub.callsFake(() => {
|
|
5076
|
+
return Promise.resolve({
|
|
5077
|
+
body: {
|
|
5078
|
+
locus: {},
|
|
5079
|
+
mediaConnections: [
|
|
5080
|
+
{
|
|
5081
|
+
remoteSdp:
|
|
5082
|
+
'{"audioMuted":false,"videoMuted":true,"roapMessage":{"messageType":"ANSWER","version":"2","seq":1,"sdps":["v=0\\r\\no=linus 0 1 IN IP4 23.89.67.4\\r\\ns=-\\r\\nc=IN IP4 23.89.67.4\\r\\n"],"headers":["noOkInTransaction"]},"type":"SDP"}',
|
|
5083
|
+
},
|
|
5084
|
+
],
|
|
5085
|
+
},
|
|
5086
|
+
});
|
|
5087
|
+
});
|
|
5088
|
+
|
|
5089
|
+
sinon.stub(meeting, 'closePeerConnections');
|
|
5090
|
+
sinon.stub(meeting.mediaProperties, 'unsetPeerConnection');
|
|
5091
|
+
sinon.stub(meeting.locusMediaRequest, 'downgradeFromMultistreamToTranscoded');
|
|
5092
|
+
});
|
|
5093
|
+
|
|
5094
|
+
const runCheck = async (turnServerInfo, forceTurnDiscovery) => {
|
|
5095
|
+
// we're calling addMediaInternal() with mic stream,
|
|
5096
|
+
// so that we also verify that audioMute, videoMute info is correctly sent to backend
|
|
5097
|
+
const addMediaPromise = meeting.addMediaInternal(
|
|
5098
|
+
() => '',
|
|
5099
|
+
turnServerInfo,
|
|
5100
|
+
forceTurnDiscovery,
|
|
5101
|
+
{
|
|
5102
|
+
localStreams: {microphone: fakeMicrophoneStream},
|
|
5103
|
+
}
|
|
5104
|
+
);
|
|
5105
|
+
await testUtils.flushPromises();
|
|
5106
|
+
await simulateRoapOffer(false);
|
|
5107
|
+
|
|
5108
|
+
// check MultistreamRoapMediaConnection was created correctly
|
|
5109
|
+
checkMediaConnectionCreated({
|
|
5110
|
+
expectMultistream: true,
|
|
5111
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
5112
|
+
localStreams: {
|
|
5113
|
+
audio: fakeMicrophoneStream,
|
|
5114
|
+
video: undefined,
|
|
5115
|
+
screenShareVideo: undefined,
|
|
5116
|
+
screenShareAudio: undefined,
|
|
5117
|
+
},
|
|
5118
|
+
direction: {
|
|
5119
|
+
audio: 'sendrecv',
|
|
5120
|
+
video: 'sendrecv',
|
|
5121
|
+
screenShare: 'recvonly',
|
|
5122
|
+
},
|
|
5123
|
+
remoteQualityLevel: 'HIGH',
|
|
5124
|
+
expectedDebugId,
|
|
5125
|
+
meetingId: meeting.id,
|
|
5126
|
+
});
|
|
5127
|
+
|
|
5128
|
+
// check that stats analyzer was created with the right config and store the reference to it so that we can later check that it was stopped
|
|
5129
|
+
assert.calledOnceWithExactly(
|
|
5130
|
+
mockStatsAnalyzerCtor,
|
|
5131
|
+
sinon.match({
|
|
5132
|
+
isMultistream: true,
|
|
5133
|
+
})
|
|
5134
|
+
);
|
|
5135
|
+
const initialStatsAnalyzer = mockStatsAnalyzerCtor.returnValues[0];
|
|
5136
|
+
mockStatsAnalyzerCtor.resetHistory();
|
|
5137
|
+
|
|
5138
|
+
// TURN discovery was done (if needed)
|
|
5139
|
+
if (turnServerInfo) {
|
|
5140
|
+
assert.notCalled(meeting.roap.doTurnDiscovery);
|
|
5141
|
+
} else {
|
|
5142
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false, false);
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
5146
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
5147
|
+
|
|
5148
|
+
await testUtils.flushPromises();
|
|
5149
|
+
|
|
5150
|
+
// at this point the meeting should have been downgraded to transcoded
|
|
5151
|
+
assert.equal(meeting.isMultistream, false);
|
|
5152
|
+
|
|
5153
|
+
// old stats analyzer stopped and new one created
|
|
5154
|
+
assert.calledOnce(initialStatsAnalyzer.stopAnalyzer);
|
|
5155
|
+
assert.calledOnceWithExactly(
|
|
5156
|
+
mockStatsAnalyzerCtor,
|
|
5157
|
+
sinon.match({
|
|
5158
|
+
isMultistream: false,
|
|
5159
|
+
})
|
|
5160
|
+
);
|
|
5161
|
+
|
|
5162
|
+
// and correct cleanup of other things should have been done
|
|
5163
|
+
assert.calledOnceWithExactly(meeting.closePeerConnections, false);
|
|
5164
|
+
assert.calledOnceWithExactly(meeting.mediaProperties.unsetPeerConnection);
|
|
5165
|
+
assert.calledOnceWithExactly(
|
|
5166
|
+
meeting.locusMediaRequest.downgradeFromMultistreamToTranscoded
|
|
5167
|
+
);
|
|
5168
|
+
|
|
5169
|
+
// new connection should have been created
|
|
5170
|
+
checkMediaConnectionCreated({
|
|
5171
|
+
expectMultistream: false,
|
|
5172
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
5173
|
+
localStreams: {
|
|
5174
|
+
audio: fakeMicrophoneStream,
|
|
5175
|
+
video: undefined,
|
|
5176
|
+
screenShareVideo: undefined,
|
|
5177
|
+
screenShareAudio: undefined,
|
|
5178
|
+
},
|
|
5179
|
+
direction: {
|
|
5180
|
+
audio: 'sendrecv',
|
|
5181
|
+
video: 'sendrecv',
|
|
5182
|
+
screenShare: 'recvonly',
|
|
5183
|
+
},
|
|
5184
|
+
remoteQualityLevel: 'HIGH',
|
|
5185
|
+
expectedDebugId,
|
|
5186
|
+
meetingId: meeting.id,
|
|
5187
|
+
});
|
|
5188
|
+
|
|
5189
|
+
// and new TURN discovery done (no matter if it was being done before or not)
|
|
5190
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, true, true);
|
|
5191
|
+
|
|
5192
|
+
// simulate new offer
|
|
5193
|
+
await simulateRoapOffer(false);
|
|
5194
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
5195
|
+
|
|
5196
|
+
// overall there should have been 2 calls to locusMediaRequestStub, because 2 offers were sent
|
|
5197
|
+
assert.calledTwice(locusMediaRequestStub);
|
|
5198
|
+
|
|
5199
|
+
// simulate answer being processed correctly
|
|
5200
|
+
transcodedEventListeners[MediaConnectionEventNames.REMOTE_SDP_ANSWER_PROCESSED]();
|
|
5201
|
+
|
|
5202
|
+
// check that addMedia finally resolved
|
|
5203
|
+
await addMediaPromise;
|
|
5204
|
+
};
|
|
5205
|
+
|
|
5206
|
+
it('addMedia() falls back to transcoded if SDP answer is not from Homer', async () => {
|
|
5207
|
+
// call addMediaInternal like addMedia() does it
|
|
5208
|
+
await runCheck(undefined, false);
|
|
5209
|
+
});
|
|
5210
|
+
|
|
5211
|
+
it('addMediaInternal() correctly falls back to transcoded if SDP answer is not from Homer (joinWithMedia() case)', async () => {
|
|
5212
|
+
// call addMediaInternal the way joinWithMedia() does it - with TURN info already provided
|
|
5213
|
+
// and check that when we fallback to transcoded we still do another TURN discovery
|
|
5214
|
+
await runCheck(
|
|
5215
|
+
{
|
|
5216
|
+
url: 'turns:turn-server-url:443?transport=tcp',
|
|
5217
|
+
username: 'turn user',
|
|
5218
|
+
password: 'turn password',
|
|
5219
|
+
},
|
|
5220
|
+
false
|
|
5221
|
+
);
|
|
5222
|
+
});
|
|
5223
|
+
|
|
5224
|
+
it('addMediaInternal() correctly falls back to transcoded if SDP answer is not from Homer (joinWithMedia() retry case)', async () => {
|
|
5225
|
+
// call addMediaInternal the way joinWithMedia() does it when it does a retry - with TURN info already provided
|
|
5226
|
+
// but also with forceTurnDiscovery=true - this shouldn't affect the flow for fallback to transcoded in any way
|
|
5227
|
+
// but doing it just for completeness
|
|
5228
|
+
await runCheck(
|
|
5229
|
+
{
|
|
5230
|
+
url: 'turns:turn-server-url:443?transport=tcp',
|
|
5231
|
+
username: 'turn user',
|
|
5232
|
+
password: 'turn password',
|
|
5233
|
+
},
|
|
5234
|
+
true
|
|
5235
|
+
);
|
|
5236
|
+
});
|
|
5237
|
+
});
|
|
5238
|
+
}
|
|
4884
5239
|
})
|
|
4885
5240
|
);
|
|
4886
5241
|
|
|
@@ -4958,6 +5313,11 @@ describe('plugin-meetings', () => {
|
|
|
4958
5313
|
meeting.logger.error = sinon.stub().returns(true);
|
|
4959
5314
|
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
4960
5315
|
webex.internal.voicea.off = sinon.stub().returns(true);
|
|
5316
|
+
meeting.stopTranscription = sinon.stub();
|
|
5317
|
+
meeting.transcription = {};
|
|
5318
|
+
|
|
5319
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
5320
|
+
webex.internal.llm.off = sinon.stub();
|
|
4961
5321
|
|
|
4962
5322
|
// A meeting needs to be joined to leave
|
|
4963
5323
|
meeting.meetingState = 'ACTIVE';
|
|
@@ -4978,6 +5338,9 @@ describe('plugin-meetings', () => {
|
|
|
4978
5338
|
assert.calledOnce(meeting.closePeerConnections);
|
|
4979
5339
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
|
4980
5340
|
assert.calledOnce(meeting.unsetPeerConnections);
|
|
5341
|
+
assert.calledOnce(meeting.stopTranscription);
|
|
5342
|
+
assert.calledOnce(meeting.annotation.deregisterEvents);
|
|
5343
|
+
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
4981
5344
|
});
|
|
4982
5345
|
|
|
4983
5346
|
it('should reset call diagnostic latencies correctly', async () => {
|
|
@@ -5965,6 +6328,38 @@ describe('plugin-meetings', () => {
|
|
|
5965
6328
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
5966
6329
|
});
|
|
5967
6330
|
|
|
6331
|
+
it('handles meetingInfoProvider not reach JBH', async () => {
|
|
6332
|
+
meeting.destination = FAKE_DESTINATION;
|
|
6333
|
+
meeting.destinationType = FAKE_TYPE;
|
|
6334
|
+
meeting.attrs.meetingInfoProvider = {
|
|
6335
|
+
fetchMeetingInfo: sinon
|
|
6336
|
+
.stub()
|
|
6337
|
+
.throws(new MeetingInfoV2JoinForbiddenError(403003, FAKE_MEETING_INFO)),
|
|
6338
|
+
};
|
|
6339
|
+
|
|
6340
|
+
await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), JoinForbiddenError);
|
|
6341
|
+
|
|
6342
|
+
assert.calledWith(
|
|
6343
|
+
meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
|
|
6344
|
+
FAKE_DESTINATION,
|
|
6345
|
+
FAKE_TYPE,
|
|
6346
|
+
null,
|
|
6347
|
+
null,
|
|
6348
|
+
undefined,
|
|
6349
|
+
'locus-id',
|
|
6350
|
+
{},
|
|
6351
|
+
{meetingId: meeting.id, sendCAevents: true}
|
|
6352
|
+
);
|
|
6353
|
+
|
|
6354
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6355
|
+
assert.equal(meeting.meetingInfoFailureCode, 403003);
|
|
6356
|
+
assert.equal(
|
|
6357
|
+
meeting.meetingInfoFailureReason,
|
|
6358
|
+
MEETING_INFO_FAILURE_REASON.NOT_REACH_JBH
|
|
6359
|
+
);
|
|
6360
|
+
assert.equal(meeting.requiredCaptcha, null);
|
|
6361
|
+
});
|
|
6362
|
+
|
|
5968
6363
|
it('handles meetingInfoProvider policy error', async () => {
|
|
5969
6364
|
meeting.destination = FAKE_DESTINATION;
|
|
5970
6365
|
meeting.destinationType = FAKE_TYPE;
|
|
@@ -6332,29 +6727,74 @@ describe('plugin-meetings', () => {
|
|
|
6332
6727
|
assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
|
|
6333
6728
|
});
|
|
6334
6729
|
|
|
6335
|
-
it('handles
|
|
6730
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need registration', async () => {
|
|
6336
6731
|
meeting.destination = FAKE_DESTINATION;
|
|
6337
6732
|
meeting.destinationType = FAKE_TYPE;
|
|
6338
6733
|
meeting.attrs.meetingInfoProvider = {
|
|
6339
6734
|
fetchMeetingInfo: sinon
|
|
6340
6735
|
.stub()
|
|
6341
6736
|
.throws(
|
|
6342
|
-
new
|
|
6737
|
+
new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
|
|
6343
6738
|
),
|
|
6344
6739
|
};
|
|
6345
6740
|
|
|
6346
6741
|
await assert.isRejected(
|
|
6347
6742
|
meeting.fetchMeetingInfo({sendCAevents: true}),
|
|
6348
|
-
|
|
6743
|
+
JoinWebinarError
|
|
6349
6744
|
);
|
|
6350
6745
|
|
|
6351
6746
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6352
|
-
assert.equal(meeting.meetingInfoFailureCode, 403021);
|
|
6353
6747
|
assert.equal(
|
|
6354
6748
|
meeting.meetingInfoFailureReason,
|
|
6355
6749
|
MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION
|
|
6356
6750
|
);
|
|
6357
6751
|
});
|
|
6752
|
+
|
|
6753
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need join with webcast', async () => {
|
|
6754
|
+
meeting.destination = FAKE_DESTINATION;
|
|
6755
|
+
meeting.destinationType = FAKE_TYPE;
|
|
6756
|
+
meeting.attrs.meetingInfoProvider = {
|
|
6757
|
+
fetchMeetingInfo: sinon
|
|
6758
|
+
.stub()
|
|
6759
|
+
.throws(
|
|
6760
|
+
new MeetingInfoV2JoinWebinarError(403026, FAKE_MEETING_INFO, 'a message')
|
|
6761
|
+
),
|
|
6762
|
+
};
|
|
6763
|
+
|
|
6764
|
+
await assert.isRejected(
|
|
6765
|
+
meeting.fetchMeetingInfo({sendCAevents: true}),
|
|
6766
|
+
JoinWebinarError
|
|
6767
|
+
);
|
|
6768
|
+
|
|
6769
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6770
|
+
assert.equal(
|
|
6771
|
+
meeting.meetingInfoFailureReason,
|
|
6772
|
+
MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST
|
|
6773
|
+
);
|
|
6774
|
+
});
|
|
6775
|
+
|
|
6776
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need registrationId', async () => {
|
|
6777
|
+
meeting.destination = FAKE_DESTINATION;
|
|
6778
|
+
meeting.destinationType = FAKE_TYPE;
|
|
6779
|
+
meeting.attrs.meetingInfoProvider = {
|
|
6780
|
+
fetchMeetingInfo: sinon
|
|
6781
|
+
.stub()
|
|
6782
|
+
.throws(
|
|
6783
|
+
new MeetingInfoV2JoinWebinarError(403037, FAKE_MEETING_INFO, 'a message')
|
|
6784
|
+
),
|
|
6785
|
+
};
|
|
6786
|
+
|
|
6787
|
+
await assert.isRejected(
|
|
6788
|
+
meeting.fetchMeetingInfo({sendCAevents: true}),
|
|
6789
|
+
JoinWebinarError
|
|
6790
|
+
);
|
|
6791
|
+
|
|
6792
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
6793
|
+
assert.equal(
|
|
6794
|
+
meeting.meetingInfoFailureReason,
|
|
6795
|
+
MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID
|
|
6796
|
+
);
|
|
6797
|
+
});
|
|
6358
6798
|
});
|
|
6359
6799
|
|
|
6360
6800
|
describe('#refreshPermissionToken', () => {
|
|
@@ -6815,6 +7255,9 @@ describe('plugin-meetings', () => {
|
|
|
6815
7255
|
meeting.transcription = {};
|
|
6816
7256
|
meeting.stopTranscription = sinon.stub();
|
|
6817
7257
|
|
|
7258
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
|
7259
|
+
webex.internal.llm.off = sinon.stub();
|
|
7260
|
+
|
|
6818
7261
|
// A meeting needs to be joined to end
|
|
6819
7262
|
meeting.meetingState = 'ACTIVE';
|
|
6820
7263
|
meeting.state = 'JOINED';
|
|
@@ -6835,6 +7278,9 @@ describe('plugin-meetings', () => {
|
|
|
6835
7278
|
assert.calledOnce(meeting?.unsetRemoteStreams);
|
|
6836
7279
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
6837
7280
|
assert.calledOnce(meeting?.stopTranscription);
|
|
7281
|
+
|
|
7282
|
+
assert.called(meeting.annotation.deregisterEvents);
|
|
7283
|
+
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
|
6838
7284
|
});
|
|
6839
7285
|
});
|
|
6840
7286
|
|
|
@@ -7817,7 +8263,9 @@ describe('plugin-meetings', () => {
|
|
|
7817
8263
|
});
|
|
7818
8264
|
|
|
7819
8265
|
it('should collect ice candidates', () => {
|
|
7820
|
-
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
|
|
8266
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
|
|
8267
|
+
candidate: {candidate: 'candidate'},
|
|
8268
|
+
});
|
|
7821
8269
|
|
|
7822
8270
|
assert.equal(meeting.iceCandidatesCount, 1);
|
|
7823
8271
|
});
|
|
@@ -8123,10 +8571,10 @@ describe('plugin-meetings', () => {
|
|
|
8123
8571
|
meeting.statsAnalyzer.stopAnalyzer = sinon.stub().resolves();
|
|
8124
8572
|
meeting.reconnectionManager = {
|
|
8125
8573
|
reconnect: sinon.stub().resolves(),
|
|
8126
|
-
resetReconnectionTimer: () => {}
|
|
8574
|
+
resetReconnectionTimer: () => {},
|
|
8127
8575
|
};
|
|
8128
8576
|
meeting.currentMediaStatus = {
|
|
8129
|
-
video: true
|
|
8577
|
+
video: true,
|
|
8130
8578
|
};
|
|
8131
8579
|
|
|
8132
8580
|
await mockFailedEvent();
|
|
@@ -8408,8 +8856,7 @@ describe('plugin-meetings', () => {
|
|
|
8408
8856
|
assert.calledWith(meeting.roapMessageReceived, fakeAnswer);
|
|
8409
8857
|
});
|
|
8410
8858
|
|
|
8411
|
-
|
|
8412
|
-
const fakeError = new Error('fake error');
|
|
8859
|
+
const runOfferSendingFailureTest = async (fakeError, canProceed, expectedErrorCode) => {
|
|
8413
8860
|
const clock = sinon.useFakeTimers();
|
|
8414
8861
|
sinon.spy(clock, 'clearTimeout');
|
|
8415
8862
|
meeting.deferSDPAnswer = {reject: sinon.stub()};
|
|
@@ -8447,19 +8894,31 @@ describe('plugin-meetings', () => {
|
|
|
8447
8894
|
assert.equal(meeting.sdpResponseTimer, undefined);
|
|
8448
8895
|
|
|
8449
8896
|
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
|
8450
|
-
clientErrorCode:
|
|
8897
|
+
clientErrorCode: expectedErrorCode,
|
|
8451
8898
|
});
|
|
8452
8899
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
|
8453
8900
|
name: 'client.media-engine.remote-sdp-received',
|
|
8454
8901
|
payload: {
|
|
8455
|
-
canProceed
|
|
8456
|
-
errors: [{errorCode:
|
|
8902
|
+
canProceed,
|
|
8903
|
+
errors: [{errorCode: expectedErrorCode, fatal: true}],
|
|
8457
8904
|
},
|
|
8458
8905
|
options: {
|
|
8459
8906
|
meetingId: meeting.id,
|
|
8460
8907
|
rawError: fakeError,
|
|
8461
8908
|
},
|
|
8462
8909
|
});
|
|
8910
|
+
};
|
|
8911
|
+
|
|
8912
|
+
it('handles OFFER message correctly when request fails', async () => {
|
|
8913
|
+
const fakeError = new Error('fake error');
|
|
8914
|
+
|
|
8915
|
+
await runOfferSendingFailureTest(fakeError, false, 2007);
|
|
8916
|
+
});
|
|
8917
|
+
|
|
8918
|
+
it('handles OFFER message correctly when we get a non-homer answer', async () => {
|
|
8919
|
+
const fakeError = new MultistreamNotSupportedError();
|
|
8920
|
+
|
|
8921
|
+
await runOfferSendingFailureTest(fakeError, true, 2012);
|
|
8463
8922
|
});
|
|
8464
8923
|
|
|
8465
8924
|
it('handles ANSWER message correctly', () => {
|
|
@@ -8662,6 +9121,7 @@ describe('plugin-meetings', () => {
|
|
|
8662
9121
|
});
|
|
8663
9122
|
});
|
|
8664
9123
|
});
|
|
9124
|
+
|
|
8665
9125
|
describe('#setUpLocusInfoSelfListener', () => {
|
|
8666
9126
|
it('listens to the self unadmitted guest event', (done) => {
|
|
8667
9127
|
meeting.startKeepAlive = sinon.stub();
|
|
@@ -8756,6 +9216,27 @@ describe('plugin-meetings', () => {
|
|
|
8756
9216
|
);
|
|
8757
9217
|
});
|
|
8758
9218
|
|
|
9219
|
+
it('listens to the brb state changed event', () => {
|
|
9220
|
+
const assertBrb = (enabled) => {
|
|
9221
|
+
meeting.brbState = createBrbState(meeting, false);
|
|
9222
|
+
meeting.locusInfo.emit(
|
|
9223
|
+
{ function: 'test', file: 'test' },
|
|
9224
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
|
|
9225
|
+
{ brb: { enabled } },
|
|
9226
|
+
)
|
|
9227
|
+
assert.calledWithExactly(
|
|
9228
|
+
TriggerProxy.trigger,
|
|
9229
|
+
meeting,
|
|
9230
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
9231
|
+
EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
|
|
9232
|
+
{ payload: { brb: { enabled } } },
|
|
9233
|
+
);
|
|
9234
|
+
}
|
|
9235
|
+
|
|
9236
|
+
assertBrb(true);
|
|
9237
|
+
assertBrb(false);
|
|
9238
|
+
})
|
|
9239
|
+
|
|
8759
9240
|
it('listens to the interpretation changed event', () => {
|
|
8760
9241
|
meeting.simultaneousInterpretation.updateSelfInterpretation = sinon.stub();
|
|
8761
9242
|
|
|
@@ -9054,7 +9535,7 @@ describe('plugin-meetings', () => {
|
|
|
9054
9535
|
{state}
|
|
9055
9536
|
);
|
|
9056
9537
|
|
|
9057
|
-
assert.calledOnceWithExactly(
|
|
9538
|
+
assert.calledOnceWithExactly(meeting.webinar.updatePracticeSessionStatus, state);
|
|
9058
9539
|
assert.calledWith(
|
|
9059
9540
|
TriggerProxy.trigger,
|
|
9060
9541
|
meeting,
|
|
@@ -9537,15 +10018,44 @@ describe('plugin-meetings', () => {
|
|
|
9537
10018
|
describe('#closePeerConnections', () => {
|
|
9538
10019
|
it('should close the webrtc media connection, and return a promise', async () => {
|
|
9539
10020
|
const setNetworkStatusSpy = sinon.spy(meeting, 'setNetworkStatus');
|
|
9540
|
-
|
|
10021
|
+
const fakeWebrtcMediaConnection = {close: sinon.stub()};
|
|
10022
|
+
meeting.mediaProperties.webrtcMediaConnection = fakeWebrtcMediaConnection;
|
|
10023
|
+
|
|
10024
|
+
meeting.audio = {id: 'fakeAudioMuteState'};
|
|
10025
|
+
meeting.video = {id: 'fakeVideoMuteState'};
|
|
10026
|
+
|
|
9541
10027
|
const pcs = meeting.closePeerConnections();
|
|
9542
10028
|
|
|
9543
10029
|
assert.exists(pcs.then);
|
|
9544
10030
|
await pcs;
|
|
9545
|
-
assert.calledOnce(
|
|
10031
|
+
assert.calledOnce(fakeWebrtcMediaConnection.close);
|
|
10032
|
+
assert.calledOnceWithExactly(setNetworkStatusSpy, undefined);
|
|
10033
|
+
assert.equal(meeting.audio, null);
|
|
10034
|
+
assert.equal(meeting.video, null);
|
|
10035
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection, null);
|
|
10036
|
+
});
|
|
10037
|
+
|
|
10038
|
+
it('should close the webrtc media connection, but keep audio and video props unchanged if called with resetMuteStates=false', async () => {
|
|
10039
|
+
const setNetworkStatusSpy = sinon.spy(meeting, 'setNetworkStatus');
|
|
10040
|
+
const fakeWebrtcMediaConnection = {close: sinon.stub()};
|
|
10041
|
+
meeting.mediaProperties.webrtcMediaConnection = fakeWebrtcMediaConnection;
|
|
10042
|
+
|
|
10043
|
+
const fakeAudio = {id: 'fakeAudioMuteState'};
|
|
10044
|
+
const fakeVideo = {id: 'fakeVideoMuteState'};
|
|
10045
|
+
|
|
10046
|
+
meeting.audio = fakeAudio;
|
|
10047
|
+
meeting.video = fakeVideo;
|
|
10048
|
+
|
|
10049
|
+
await meeting.closePeerConnections(false);
|
|
10050
|
+
|
|
10051
|
+
assert.calledOnce(fakeWebrtcMediaConnection.close);
|
|
9546
10052
|
assert.calledOnceWithExactly(setNetworkStatusSpy, undefined);
|
|
10053
|
+
assert.equal(meeting.audio, fakeAudio);
|
|
10054
|
+
assert.equal(meeting.video, fakeVideo);
|
|
10055
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection, null);
|
|
9547
10056
|
});
|
|
9548
10057
|
});
|
|
10058
|
+
|
|
9549
10059
|
describe('#unsetPeerConnections', () => {
|
|
9550
10060
|
it('should unset the peer connections', () => {
|
|
9551
10061
|
meeting.mediaProperties.unsetPeerConnection = sinon.stub().returns(true);
|
|
@@ -10674,6 +11184,7 @@ describe('plugin-meetings', () => {
|
|
|
10674
11184
|
meeting.webex.internal.llm.on = sinon.stub();
|
|
10675
11185
|
meeting.webex.internal.llm.off = sinon.stub();
|
|
10676
11186
|
meeting.processRelayEvent = sinon.stub();
|
|
11187
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
|
10677
11188
|
});
|
|
10678
11189
|
|
|
10679
11190
|
it('does not connect if the call is not joined yet', async () => {
|
|
@@ -10805,6 +11316,19 @@ describe('plugin-meetings', () => {
|
|
|
10805
11316
|
meeting.processRelayEvent
|
|
10806
11317
|
);
|
|
10807
11318
|
});
|
|
11319
|
+
|
|
11320
|
+
|
|
11321
|
+
it('connect ps data channel if ps started in webinar', async () => {
|
|
11322
|
+
meeting.joinedWith = {state: 'JOINED'};
|
|
11323
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
|
|
11324
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
|
|
11325
|
+
await meeting.updateLLMConnection();
|
|
11326
|
+
|
|
11327
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
11328
|
+
assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
|
|
11329
|
+
|
|
11330
|
+
});
|
|
11331
|
+
|
|
10808
11332
|
});
|
|
10809
11333
|
|
|
10810
11334
|
describe('#setLocus', () => {
|
|
@@ -10996,6 +11520,7 @@ describe('plugin-meetings', () => {
|
|
|
10996
11520
|
beforeEach(() => {
|
|
10997
11521
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
10998
11522
|
meeting.deviceUrl = 'my-web-url';
|
|
11523
|
+
meeting.locusInfo.info = {isWebinar: false};
|
|
10999
11524
|
});
|
|
11000
11525
|
|
|
11001
11526
|
const USER_IDS = {
|
|
@@ -11221,13 +11746,24 @@ describe('plugin-meetings', () => {
|
|
|
11221
11746
|
|
|
11222
11747
|
activeSharingId.whiteboard = beneficiaryId;
|
|
11223
11748
|
|
|
11224
|
-
eventTrigger.share.push({
|
|
11749
|
+
eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
|
|
11750
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
11751
|
+
functionName: 'remoteShare',
|
|
11752
|
+
eventPayload: {
|
|
11753
|
+
memberId: null,
|
|
11754
|
+
url,
|
|
11755
|
+
shareInstanceId,
|
|
11756
|
+
annotationInfo: undefined,
|
|
11757
|
+
resourceType: undefined,
|
|
11758
|
+
},
|
|
11759
|
+
} : {
|
|
11225
11760
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
|
11226
11761
|
functionName: 'startWhiteboardShare',
|
|
11227
11762
|
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
|
11228
11763
|
});
|
|
11229
11764
|
|
|
11230
|
-
shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11765
|
+
shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11766
|
+
|
|
11231
11767
|
}
|
|
11232
11768
|
|
|
11233
11769
|
if (eventTrigger.member) {
|
|
@@ -11259,13 +11795,24 @@ describe('plugin-meetings', () => {
|
|
|
11259
11795
|
newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
|
|
11260
11796
|
newPayload.current.content.beneficiaryId = otherBeneficiaryId;
|
|
11261
11797
|
|
|
11262
|
-
eventTrigger.share.push({
|
|
11798
|
+
eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
|
|
11799
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
11800
|
+
functionName: 'remoteShare',
|
|
11801
|
+
eventPayload: {
|
|
11802
|
+
memberId: null,
|
|
11803
|
+
url,
|
|
11804
|
+
shareInstanceId,
|
|
11805
|
+
annotationInfo: undefined,
|
|
11806
|
+
resourceType: undefined,
|
|
11807
|
+
},
|
|
11808
|
+
} : {
|
|
11263
11809
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
|
11264
11810
|
functionName: 'startWhiteboardShare',
|
|
11265
11811
|
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
|
11266
11812
|
});
|
|
11267
11813
|
|
|
11268
|
-
shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11814
|
+
shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
|
11815
|
+
|
|
11269
11816
|
} else {
|
|
11270
11817
|
eventTrigger.share.push({
|
|
11271
11818
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
|
|
@@ -11392,6 +11939,38 @@ describe('plugin-meetings', () => {
|
|
|
11392
11939
|
assert.exists(meeting.setUpLocusMediaSharesListener);
|
|
11393
11940
|
});
|
|
11394
11941
|
|
|
11942
|
+
describe('Whiteboard Share - Webinar Attendee', () => {
|
|
11943
|
+
it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
|
|
11944
|
+
// Set the webinar attendee flag
|
|
11945
|
+
meeting.webinar = { selfIsAttendee: true };
|
|
11946
|
+
meeting.locusInfo.info.isWebinar = true;
|
|
11947
|
+
|
|
11948
|
+
// Step 1: Start sharing whiteboard A
|
|
11949
|
+
const data1 = generateData(
|
|
11950
|
+
blankPayload, // Initial payload
|
|
11951
|
+
true, // isGranting: Granting share
|
|
11952
|
+
false, // isContent: Whiteboard (not content)
|
|
11953
|
+
USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
|
|
11954
|
+
RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
|
|
11955
|
+
);
|
|
11956
|
+
|
|
11957
|
+
// Step 2: Stop sharing whiteboard A
|
|
11958
|
+
const data2 = generateData(
|
|
11959
|
+
data1.payload, // Updated payload from Step 1
|
|
11960
|
+
false, // isGranting: Stopping share
|
|
11961
|
+
false, // isContent: Whiteboard
|
|
11962
|
+
USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
|
|
11963
|
+
);
|
|
11964
|
+
|
|
11965
|
+
// Validate the payload changes and status updates
|
|
11966
|
+
payloadTestHelper([data1]);
|
|
11967
|
+
|
|
11968
|
+
// Specific assertions for webinar attendee status
|
|
11969
|
+
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
|
11970
|
+
});
|
|
11971
|
+
});
|
|
11972
|
+
|
|
11973
|
+
|
|
11395
11974
|
describe('Whiteboard A --> Whiteboard B', () => {
|
|
11396
11975
|
it('Scenario #1: you share both whiteboards', () => {
|
|
11397
11976
|
const data1 = generateData(
|
|
@@ -12067,9 +12646,12 @@ describe('plugin-meetings', () => {
|
|
|
12067
12646
|
it('startKeepAlive starts the keep alive', async () => {
|
|
12068
12647
|
meeting.meetingRequest.keepAlive = sinon.stub().returns(Promise.resolve());
|
|
12069
12648
|
|
|
12649
|
+
const keepAliveUrl1 = 'keep.alive.url1';
|
|
12650
|
+
const keepAliveUrl2 = 'keep.alive.url2';
|
|
12651
|
+
|
|
12070
12652
|
assert.isNull(meeting.keepAliveTimerId);
|
|
12071
12653
|
meeting.joinedWith = {
|
|
12072
|
-
keepAliveUrl:
|
|
12654
|
+
keepAliveUrl: keepAliveUrl1,
|
|
12073
12655
|
keepAliveSecs: defaultKeepAliveSecs,
|
|
12074
12656
|
};
|
|
12075
12657
|
meeting.startKeepAlive();
|
|
@@ -12078,12 +12660,15 @@ describe('plugin-meetings', () => {
|
|
|
12078
12660
|
assert.notCalled(meeting.meetingRequest.keepAlive);
|
|
12079
12661
|
await progressTime(defaultExpectedInterval);
|
|
12080
12662
|
assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {
|
|
12081
|
-
keepAliveUrl:
|
|
12663
|
+
keepAliveUrl: keepAliveUrl1,
|
|
12082
12664
|
});
|
|
12665
|
+
// joinedWith keep alive url might change (when we fallback from multistream to transcoded)
|
|
12666
|
+
meeting.joinedWith.keepAliveUrl = keepAliveUrl2;
|
|
12667
|
+
|
|
12083
12668
|
await progressTime(defaultExpectedInterval);
|
|
12084
12669
|
assert.calledTwice(meeting.meetingRequest.keepAlive);
|
|
12085
|
-
assert.
|
|
12086
|
-
keepAliveUrl:
|
|
12670
|
+
assert.calledWith(meeting.meetingRequest.keepAlive, {
|
|
12671
|
+
keepAliveUrl: keepAliveUrl2,
|
|
12087
12672
|
});
|
|
12088
12673
|
});
|
|
12089
12674
|
it('startKeepAlive handles existing keepAliveTimerId', async () => {
|
|
@@ -12664,7 +13249,7 @@ describe('plugin-meetings', () => {
|
|
|
12664
13249
|
|
|
12665
13250
|
describe('#roapMessageReceived', () => {
|
|
12666
13251
|
it('calls roapMessageReceived on the webrtc media connection', () => {
|
|
12667
|
-
const fakeMessage = {messageType: '
|
|
13252
|
+
const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
|
|
12668
13253
|
|
|
12669
13254
|
const getMediaServer = sinon.stub(MeetingsUtil, 'getMediaServer').returns('homer');
|
|
12670
13255
|
|
|
@@ -12681,5 +13266,47 @@ describe('plugin-meetings', () => {
|
|
|
12681
13266
|
assert.calledOnceWithExactly(getMediaServer, 'fake sdp');
|
|
12682
13267
|
assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'homer');
|
|
12683
13268
|
});
|
|
13269
|
+
|
|
13270
|
+
it('throws MultistreamNotSupportedError if we get a non-homer SDP answer', async () => {
|
|
13271
|
+
const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
|
|
13272
|
+
|
|
13273
|
+
meeting.isMultistream = true;
|
|
13274
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
13275
|
+
roapMessageReceived: sinon.stub(),
|
|
13276
|
+
};
|
|
13277
|
+
|
|
13278
|
+
sinon.stub(MeetingsUtil, 'getMediaServer').returns('linus');
|
|
13279
|
+
|
|
13280
|
+
try {
|
|
13281
|
+
await meeting.roapMessageReceived(fakeMessage);
|
|
13282
|
+
|
|
13283
|
+
assert.fail('Expected MultistreamNotSupportedError to be thrown');
|
|
13284
|
+
} catch(e) {
|
|
13285
|
+
assert.isTrue(e instanceof MultistreamNotSupportedError);
|
|
13286
|
+
}
|
|
13287
|
+
|
|
13288
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived);
|
|
13289
|
+
});
|
|
13290
|
+
|
|
13291
|
+
it('does not call getMediaServer for a roap message other than ANSWER', async () => {
|
|
13292
|
+
const fakeMessage = {messageType: 'ERROR', sdp: 'fake sdp'};
|
|
13293
|
+
|
|
13294
|
+
meeting.isMultistream = true;
|
|
13295
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
13296
|
+
roapMessageReceived: sinon.stub(),
|
|
13297
|
+
};
|
|
13298
|
+
meeting.mediaProperties.webrtcMediaConnection.mediaServer = 'linus';
|
|
13299
|
+
|
|
13300
|
+
const getMediaServerStub = sinon.stub(MeetingsUtil, 'getMediaServer').returns('something');
|
|
13301
|
+
|
|
13302
|
+
meeting.roapMessageReceived(fakeMessage);
|
|
13303
|
+
|
|
13304
|
+
assert.calledOnceWithExactly(
|
|
13305
|
+
meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived,
|
|
13306
|
+
fakeMessage
|
|
13307
|
+
);
|
|
13308
|
+
assert.notCalled(getMediaServerStub);
|
|
13309
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'linus'); // check that it hasn't been overwritten
|
|
13310
|
+
});
|
|
12684
13311
|
});
|
|
12685
13312
|
});
|