@webex/plugin-meetings 3.7.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/annotation/annotation.types.d.ts +42 -0
- package/dist/annotation/constants.d.ts +31 -0
- package/dist/annotation/index.d.ts +117 -0
- package/dist/annotation/index.js +17 -0
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.d.ts +8 -0
- package/dist/breakouts/breakout.js +1 -1
- package/dist/breakouts/collection.d.ts +5 -0
- package/dist/breakouts/edit-lock-error.d.ts +15 -0
- package/dist/breakouts/events.d.ts +8 -0
- package/dist/breakouts/index.d.ts +5 -0
- package/dist/breakouts/index.js +1 -1
- package/dist/breakouts/request.d.ts +22 -0
- package/dist/breakouts/utils.d.ts +15 -0
- package/dist/common/browser-detection.d.ts +9 -0
- package/dist/common/collection.d.ts +48 -0
- package/dist/common/config.d.ts +2 -0
- package/dist/common/errors/captcha-error.d.ts +15 -0
- package/dist/common/errors/intent-to-join.d.ts +16 -0
- 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/join-meeting.d.ts +17 -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/media.d.ts +15 -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/common/errors/no-meeting-info.d.ts +14 -0
- package/dist/common/errors/parameter.d.ts +15 -0
- package/dist/common/errors/password-error.d.ts +15 -0
- package/dist/common/errors/permission.d.ts +14 -0
- package/dist/common/errors/reclaim-host-role-error.js +149 -0
- package/dist/common/errors/reclaim-host-role-error.js.map +1 -0
- package/dist/common/errors/reclaim-host-role-errors.d.ts +60 -0
- package/dist/common/errors/reconnection-in-progress.d.ts +9 -0
- package/dist/common/errors/reconnection-in-progress.js +33 -0
- package/dist/common/errors/reconnection-in-progress.js.map +1 -0
- package/dist/common/errors/reconnection.d.ts +15 -0
- package/dist/common/errors/stats.d.ts +15 -0
- package/dist/common/errors/webex-errors.d.ts +93 -0
- package/dist/common/errors/webex-meetings-error.d.ts +20 -0
- package/dist/common/events/events-scope.d.ts +17 -0
- package/dist/common/events/events.d.ts +12 -0
- package/dist/common/events/trigger-proxy.d.ts +2 -0
- package/dist/common/events/util.d.ts +2 -0
- package/dist/common/logs/logger-config.d.ts +2 -0
- package/dist/common/logs/logger-proxy.d.ts +2 -0
- package/dist/common/logs/request.d.ts +36 -0
- package/dist/common/queue.d.ts +34 -0
- package/dist/config.d.ts +72 -0
- package/dist/config.js +2 -1
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts +1088 -0
- package/dist/constants.js +68 -6
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.d.ts +4 -0
- package/dist/controls-options-manager/enums.d.ts +15 -0
- package/dist/controls-options-manager/index.d.ts +136 -0
- package/dist/controls-options-manager/types.d.ts +43 -0
- package/dist/controls-options-manager/util.d.ts +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +16 -11
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.d.ts +2 -0
- package/dist/interceptors/locusRetry.d.ts +27 -0
- package/dist/interpretation/collection.d.ts +5 -0
- package/dist/interpretation/index.d.ts +5 -0
- package/dist/interpretation/index.js +1 -1
- package/dist/interpretation/siLanguage.d.ts +5 -0
- package/dist/interpretation/siLanguage.js +1 -1
- package/dist/locus-info/controlsUtils.d.ts +2 -0
- package/dist/locus-info/embeddedAppsUtils.d.ts +2 -0
- package/dist/locus-info/fullState.d.ts +2 -0
- package/dist/locus-info/hostUtils.d.ts +2 -0
- package/dist/locus-info/index.d.ts +322 -0
- package/dist/locus-info/index.js +14 -3
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.d.ts +2 -0
- package/dist/locus-info/mediaSharesUtils.d.ts +2 -0
- package/dist/locus-info/parser.d.ts +272 -0
- package/dist/locus-info/selfUtils.d.ts +2 -0
- package/dist/locus-info/selfUtils.js +35 -17
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/MediaConnectionAwaiter.js +1 -0
- package/dist/media/MediaConnectionAwaiter.js.map +1 -1
- package/dist/media/index.d.ts +34 -0
- package/dist/media/properties.d.ts +93 -0
- package/dist/media/properties.js +30 -16
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.d.ts +2 -0
- package/dist/mediaQualityMetrics/config.d.ts +241 -0
- package/dist/mediaQualityMetrics/config.js +502 -0
- package/dist/mediaQualityMetrics/config.js.map +1 -0
- package/dist/meeting/brbState.js +167 -0
- package/dist/meeting/brbState.js.map +1 -0
- package/dist/meeting/effectsState.js +260 -0
- package/dist/meeting/effectsState.js.map +1 -0
- package/dist/meeting/in-meeting-actions.d.ts +167 -0
- package/dist/meeting/in-meeting-actions.js +13 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.d.ts +1825 -0
- package/dist/meeting/index.js +1331 -1051
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.d.ts +74 -0
- package/dist/meeting/locusMediaRequest.js +11 -6
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.d.ts +178 -0
- package/dist/meeting/muteState.js +1 -6
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.d.ts +295 -0
- package/dist/meeting/request.js +51 -29
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.d.ts +11 -0
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/state.d.ts +9 -0
- package/dist/meeting/util.d.ts +119 -0
- package/dist/meeting/util.js +103 -67
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting/voicea-meeting.d.ts +16 -0
- package/dist/meeting-info/collection.d.ts +20 -0
- package/dist/meeting-info/index.d.ts +69 -0
- package/dist/meeting-info/meeting-info-v2.d.ts +123 -0
- package/dist/meeting-info/meeting-info-v2.js +115 -45
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/request.d.ts +22 -0
- package/dist/meeting-info/util.d.ts +2 -0
- package/dist/meeting-info/utilv2.d.ts +2 -0
- package/dist/meeting-info/utilv2.js +6 -2
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.d.ts +40 -0
- package/dist/meetings/index.d.ts +390 -0
- package/dist/meetings/index.js +107 -55
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.d.ts +4 -0
- package/dist/meetings/meetings.types.js +2 -0
- package/dist/meetings/meetings.types.js.map +1 -1
- package/dist/meetings/request.d.ts +27 -0
- package/dist/meetings/util.d.ts +18 -0
- package/dist/meetings/util.js +1 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.d.ts +160 -0
- package/dist/member/index.js +9 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/member.types.js +17 -0
- package/dist/member/member.types.js.map +1 -0
- package/dist/member/types.d.ts +32 -0
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.d.ts +2 -0
- package/dist/member/util.js +39 -28
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.d.ts +29 -0
- package/dist/members/index.d.ts +353 -0
- package/dist/members/request.d.ts +114 -0
- package/dist/members/types.d.ts +25 -0
- package/dist/members/util.d.ts +215 -0
- package/dist/members/util.js +4 -2
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/config.js +276 -0
- package/dist/metrics/config.js.map +1 -0
- package/dist/metrics/constants.d.ts +70 -0
- package/dist/metrics/constants.js +6 -1
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.d.ts +45 -0
- package/dist/multistream/mediaRequestManager.d.ts +119 -0
- package/dist/multistream/receiveSlot.d.ts +68 -0
- package/dist/multistream/receiveSlotManager.d.ts +56 -0
- package/dist/multistream/remoteMedia.d.ts +72 -0
- package/dist/multistream/remoteMedia.js +30 -15
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.d.ts +49 -0
- package/dist/multistream/remoteMediaManager.d.ts +300 -0
- package/dist/multistream/sendSlotManager.d.ts +69 -0
- package/dist/multistream/sendSlotManager.js +24 -0
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/networkQualityMonitor/index.d.ts +70 -0
- package/dist/networkQualityMonitor/index.js +13 -19
- package/dist/networkQualityMonitor/index.js.map +1 -1
- package/dist/peer-connection-manager/index.js +671 -0
- package/dist/peer-connection-manager/index.js.map +1 -0
- package/dist/peer-connection-manager/util.js +109 -0
- package/dist/peer-connection-manager/util.js.map +1 -0
- package/dist/personal-meeting-room/index.d.ts +47 -0
- package/dist/personal-meeting-room/request.d.ts +14 -0
- package/dist/personal-meeting-room/util.d.ts +2 -0
- package/dist/reachability/clusterReachability.d.ts +109 -0
- package/dist/reachability/clusterReachability.js +12 -15
- package/dist/reachability/clusterReachability.js.map +1 -1
- package/dist/reachability/index.d.ts +105 -0
- package/dist/reachability/index.js +461 -136
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/reachability.types.js +7 -0
- package/dist/reachability/reachability.types.js.map +1 -0
- package/dist/reachability/request.d.ts +39 -0
- package/dist/reachability/request.js +21 -8
- package/dist/reachability/request.js.map +1 -1
- package/dist/reachability/util.d.ts +8 -0
- package/dist/reactions/constants.d.ts +3 -0
- package/dist/reactions/reactions.d.ts +4 -0
- package/dist/reactions/reactions.type.d.ts +52 -0
- package/dist/reconnection-manager/index.d.ts +136 -0
- package/dist/recording-controller/enums.d.ts +7 -0
- package/dist/recording-controller/enums.js +8 -4
- package/dist/recording-controller/enums.js.map +1 -1
- package/dist/recording-controller/index.d.ts +207 -0
- package/dist/recording-controller/index.js +18 -9
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/recording-controller/util.d.ts +14 -0
- package/dist/recording-controller/util.js +13 -9
- package/dist/recording-controller/util.js.map +1 -1
- package/dist/roap/collection.js +62 -0
- package/dist/roap/collection.js.map +1 -0
- package/dist/roap/handler.js +275 -0
- package/dist/roap/handler.js.map +1 -0
- package/dist/roap/index.d.ts +86 -0
- package/dist/roap/index.js +15 -15
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.d.ts +39 -0
- package/dist/roap/request.js +45 -79
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/state.js +126 -0
- package/dist/roap/state.js.map +1 -0
- package/dist/roap/turnDiscovery.d.ts +155 -0
- package/dist/roap/turnDiscovery.js +3 -6
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/roap/util.js +75 -0
- package/dist/roap/util.js.map +1 -0
- package/dist/rtcMetrics/constants.d.ts +4 -0
- package/dist/rtcMetrics/index.d.ts +61 -0
- package/dist/statsAnalyzer/global.d.ts +36 -0
- package/dist/statsAnalyzer/global.js +126 -0
- package/dist/statsAnalyzer/global.js.map +1 -0
- package/dist/statsAnalyzer/index.d.ts +217 -0
- package/dist/statsAnalyzer/index.js +1013 -0
- package/dist/statsAnalyzer/index.js.map +1 -0
- package/dist/statsAnalyzer/mqaUtil.d.ts +48 -0
- package/dist/statsAnalyzer/mqaUtil.js +179 -0
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -0
- package/dist/transcription/index.d.ts +64 -0
- 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/common/errors/reconnection-in-progress.d.ts +9 -0
- package/dist/types/config.d.ts +1 -0
- package/dist/types/constants.d.ts +53 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/locus-info/index.d.ts +2 -1
- package/dist/types/mediaQualityMetrics/config.d.ts +241 -0
- package/dist/types/meeting/brbState.d.ts +54 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +12 -0
- package/dist/types/meeting/index.d.ts +64 -14
- package/dist/types/meeting/locusMediaRequest.d.ts +6 -3
- package/dist/types/meeting/request.d.ts +14 -3
- package/dist/types/meeting/request.type.d.ts +6 -0
- package/dist/types/meeting/util.d.ts +3 -3
- package/dist/types/meeting-info/meeting-info-v2.d.ts +30 -5
- package/dist/types/meetings/index.d.ts +20 -2
- 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/members/util.d.ts +2 -0
- package/dist/types/metrics/constants.d.ts +6 -1
- package/dist/types/multistream/sendSlotManager.d.ts +8 -1
- package/dist/types/reachability/clusterReachability.d.ts +1 -10
- package/dist/types/reachability/index.d.ts +83 -36
- package/dist/types/reachability/reachability.types.d.ts +64 -0
- package/dist/types/reachability/request.d.ts +5 -1
- package/dist/types/recording-controller/enums.d.ts +5 -2
- package/dist/types/recording-controller/index.d.ts +1 -0
- package/dist/types/recording-controller/util.d.ts +2 -1
- package/dist/types/roap/request.d.ts +1 -13
- package/dist/types/statsAnalyzer/global.d.ts +36 -0
- package/dist/types/statsAnalyzer/index.d.ts +217 -0
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +48 -0
- package/dist/webinar/collection.d.ts +16 -0
- package/dist/webinar/index.d.ts +5 -0
- package/dist/webinar/index.js +390 -7
- 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 -0
- package/src/constants.ts +61 -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/media/MediaConnectionAwaiter.ts +2 -0
- package/src/media/properties.ts +34 -13
- package/src/meeting/brbState.ts +169 -0
- package/src/meeting/in-meeting-actions.ts +25 -0
- package/src/meeting/index.ts +443 -87
- package/src/meeting/locusMediaRequest.ts +11 -8
- package/src/meeting/muteState.ts +1 -6
- package/src/meeting/request.ts +30 -12
- package/src/meeting/request.type.ts +7 -0
- package/src/meeting/util.ts +32 -13
- package/src/meeting-info/meeting-info-v2.ts +83 -12
- package/src/meeting-info/utilv2.ts +17 -3
- 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/members/util.ts +1 -0
- package/src/metrics/constants.ts +6 -1
- package/src/multistream/remoteMedia.ts +28 -15
- package/src/multistream/sendSlotManager.ts +31 -0
- package/src/reachability/clusterReachability.ts +5 -15
- package/src/reachability/index.ts +311 -75
- package/src/reachability/reachability.types.ts +85 -0
- package/src/reachability/request.ts +55 -31
- package/src/recording-controller/enums.ts +5 -2
- package/src/recording-controller/index.ts +17 -4
- package/src/recording-controller/util.ts +20 -5
- package/src/roap/index.ts +14 -13
- package/src/roap/request.ts +30 -44
- package/src/roap/turnDiscovery.ts +2 -4
- package/src/webinar/index.ts +235 -9
- 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/media/properties.ts +15 -0
- package/test/unit/spec/meeting/brbState.ts +114 -0
- package/test/unit/spec/meeting/in-meeting-actions.ts +15 -1
- package/test/unit/spec/meeting/index.js +851 -107
- package/test/unit/spec/meeting/locusMediaRequest.ts +18 -11
- package/test/unit/spec/meeting/muteState.js +0 -24
- package/test/unit/spec/meeting/request.js +3 -26
- package/test/unit/spec/meeting/utils.js +73 -28
- package/test/unit/spec/meeting-info/meetinginfov2.js +46 -4
- package/test/unit/spec/meeting-info/utilv2.js +26 -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/members/utils.js +95 -0
- package/test/unit/spec/multistream/remoteMedia.ts +11 -7
- package/test/unit/spec/reachability/clusterReachability.ts +7 -0
- package/test/unit/spec/reachability/index.ts +383 -9
- package/test/unit/spec/reachability/request.js +48 -12
- package/test/unit/spec/recording-controller/index.js +61 -5
- package/test/unit/spec/recording-controller/util.js +39 -3
- package/test/unit/spec/roap/index.ts +48 -1
- package/test/unit/spec/roap/request.ts +51 -109
- package/test/unit/spec/roap/turnDiscovery.ts +202 -147
- package/test/unit/spec/webinar/index.ts +504 -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(
|
@@ -649,11 +653,10 @@ describe('plugin-meetings', () => {
|
|
649
653
|
});
|
650
654
|
|
651
655
|
const fakeRoapMessage = {id: 'fake TURN discovery message'};
|
652
|
-
const fakeReachabilityResults = {id: 'fake reachability'};
|
653
656
|
const fakeTurnServerInfo = {id: 'fake turn info'};
|
654
657
|
const fakeJoinResult = {id: 'join result'};
|
655
658
|
|
656
|
-
const joinOptions = {correlationId: '12345'};
|
659
|
+
const joinOptions = {correlationId: '12345', enableMultistream: true};
|
657
660
|
const mediaOptions = {audioEnabled: true, allowMediaInLobby: true};
|
658
661
|
|
659
662
|
let generateTurnDiscoveryRequestMessageStub;
|
@@ -662,13 +665,14 @@ describe('plugin-meetings', () => {
|
|
662
665
|
let addMediaInternalStub;
|
663
666
|
|
664
667
|
beforeEach(() => {
|
665
|
-
meeting.join = sinon.stub().
|
668
|
+
meeting.join = sinon.stub().callsFake((joinOptions) => {
|
669
|
+
meeting.isMultistream = joinOptions.enableMultistream;
|
670
|
+
return Promise.resolve(fakeJoinResult)
|
671
|
+
});
|
666
672
|
addMediaInternalStub = sinon
|
667
673
|
.stub(meeting, 'addMediaInternal')
|
668
674
|
.returns(Promise.resolve(test4));
|
669
675
|
|
670
|
-
webex.meetings.reachability.getReachabilityResults.resolves(fakeReachabilityResults);
|
671
|
-
|
672
676
|
generateTurnDiscoveryRequestMessageStub = sinon
|
673
677
|
.stub(meeting.roap, 'generateTurnDiscoveryRequestMessage')
|
674
678
|
.resolves({roapMessage: fakeRoapMessage});
|
@@ -688,7 +692,6 @@ describe('plugin-meetings', () => {
|
|
688
692
|
assert.calledOnceWithExactly(meeting.join, {
|
689
693
|
...joinOptions,
|
690
694
|
roapMessage: fakeRoapMessage,
|
691
|
-
reachability: fakeReachabilityResults,
|
692
695
|
});
|
693
696
|
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
694
697
|
assert.calledOnceWithExactly(
|
@@ -704,7 +707,7 @@ describe('plugin-meetings', () => {
|
|
704
707
|
mediaOptions
|
705
708
|
);
|
706
709
|
|
707
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
710
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
708
711
|
|
709
712
|
// resets joinWithMediaRetryInfo
|
710
713
|
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
@@ -725,7 +728,6 @@ describe('plugin-meetings', () => {
|
|
725
728
|
assert.calledOnceWithExactly(meeting.join, {
|
726
729
|
...joinOptions,
|
727
730
|
roapMessage: undefined,
|
728
|
-
reachability: fakeReachabilityResults,
|
729
731
|
});
|
730
732
|
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
731
733
|
assert.notCalled(handleTurnDiscoveryHttpResponseStub);
|
@@ -738,7 +740,7 @@ describe('plugin-meetings', () => {
|
|
738
740
|
mediaOptions
|
739
741
|
);
|
740
742
|
|
741
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
743
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
742
744
|
assert.equal(meeting.turnServerUsed, false);
|
743
745
|
});
|
744
746
|
|
@@ -757,7 +759,6 @@ describe('plugin-meetings', () => {
|
|
757
759
|
assert.calledOnceWithExactly(meeting.join, {
|
758
760
|
...joinOptions,
|
759
761
|
roapMessage: fakeRoapMessage,
|
760
|
-
reachability: fakeReachabilityResults,
|
761
762
|
});
|
762
763
|
assert.calledOnceWithExactly(generateTurnDiscoveryRequestMessageStub, meeting, true);
|
763
764
|
assert.calledOnceWithExactly(
|
@@ -774,7 +775,7 @@ describe('plugin-meetings', () => {
|
|
774
775
|
mediaOptions
|
775
776
|
);
|
776
777
|
|
777
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
778
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
778
779
|
});
|
779
780
|
|
780
781
|
it('should reject if join() fails', async () => {
|
@@ -861,7 +862,8 @@ describe('plugin-meetings', () => {
|
|
861
862
|
}
|
862
863
|
);
|
863
864
|
|
864
|
-
|
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});
|
865
867
|
|
866
868
|
// resets joinWithMediaRetryInfo
|
867
869
|
assert.deepEqual(meeting.joinWithMediaRetryInfo, {
|
@@ -950,7 +952,7 @@ describe('plugin-meetings', () => {
|
|
950
952
|
mediaOptions,
|
951
953
|
});
|
952
954
|
|
953
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: test4});
|
955
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: test4, multistreamEnabled: true});
|
954
956
|
|
955
957
|
assert.calledOnce(meeting.join);
|
956
958
|
assert.notCalled(leaveStub);
|
@@ -1044,6 +1046,7 @@ describe('plugin-meetings', () => {
|
|
1044
1046
|
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
1045
1047
|
initiateOffer: sinon.stub().resolves({}),
|
1046
1048
|
on: sinon.stub(),
|
1049
|
+
createSendSlot: sinon.stub(),
|
1047
1050
|
};
|
1048
1051
|
|
1049
1052
|
/* Setup the stubs so that the first call to addMediaInternal() fails
|
@@ -1060,12 +1063,14 @@ describe('plugin-meetings', () => {
|
|
1060
1063
|
|
1061
1064
|
sinon.stub(meeting.roap, 'doTurnDiscovery').resolves({turnServerInfo: 'fake turn info'});
|
1062
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
|
1063
1068
|
const result = await meeting.joinWithMedia({
|
1064
|
-
joinOptions,
|
1069
|
+
joinOptions: {...joinOptions, enableMultistream: false},
|
1065
1070
|
mediaOptions,
|
1066
1071
|
});
|
1067
1072
|
|
1068
|
-
assert.deepEqual(result, {join: fakeJoinResult, media: undefined});
|
1073
|
+
assert.deepEqual(result, {join: fakeJoinResult, media: undefined, multistreamEnabled: false});
|
1069
1074
|
|
1070
1075
|
assert.calledOnce(meeting.join);
|
1071
1076
|
|
@@ -1140,6 +1145,7 @@ describe('plugin-meetings', () => {
|
|
1140
1145
|
addMediaError.name = 'SdpOfferCreationError';
|
1141
1146
|
|
1142
1147
|
meeting.addMediaInternal.rejects(addMediaError);
|
1148
|
+
sinon.stub(meeting, 'leave').resolves();
|
1143
1149
|
|
1144
1150
|
await assert.isRejected(
|
1145
1151
|
meeting.joinWithMedia({
|
@@ -1168,6 +1174,7 @@ describe('plugin-meetings', () => {
|
|
1168
1174
|
type: addMediaError.name,
|
1169
1175
|
}
|
1170
1176
|
);
|
1177
|
+
assert.calledOnceWithExactly(meeting.leave, {resourceId: undefined, reason: 'joinWithMedia failure'})
|
1171
1178
|
});
|
1172
1179
|
});
|
1173
1180
|
|
@@ -1244,6 +1251,7 @@ describe('plugin-meetings', () => {
|
|
1244
1251
|
webex.internal.voicea.off = sinon.stub();
|
1245
1252
|
webex.internal.voicea.listenToEvents = sinon.stub();
|
1246
1253
|
webex.internal.voicea.turnOnCaptions = sinon.stub();
|
1254
|
+
webex.internal.voicea.deregisterEvents = sinon.stub();
|
1247
1255
|
});
|
1248
1256
|
|
1249
1257
|
it('should stop listening to voicea events and also trigger a stop event', () => {
|
@@ -1572,6 +1580,55 @@ describe('plugin-meetings', () => {
|
|
1572
1580
|
fakeProcessedReaction
|
1573
1581
|
);
|
1574
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
|
+
});
|
1575
1632
|
});
|
1576
1633
|
|
1577
1634
|
describe('#handleLLMOnline', () => {
|
@@ -1711,6 +1768,12 @@ describe('plugin-meetings', () => {
|
|
1711
1768
|
sinon.assert.called(setCorrelationIdSpy);
|
1712
1769
|
assert.equal(meeting.correlationId, '123');
|
1713
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
|
+
});
|
1714
1777
|
});
|
1715
1778
|
|
1716
1779
|
describe('failure', () => {
|
@@ -2034,6 +2097,7 @@ describe('plugin-meetings', () => {
|
|
2034
2097
|
someReachabilityMetric1: 'some value1',
|
2035
2098
|
someReachabilityMetric2: 'some value2',
|
2036
2099
|
}),
|
2100
|
+
stopReachability: sinon.stub(),
|
2037
2101
|
};
|
2038
2102
|
|
2039
2103
|
const forceRtcMetricsSend = sinon.stub().resolves();
|
@@ -2453,6 +2517,7 @@ describe('plugin-meetings', () => {
|
|
2453
2517
|
assert.calledOnce(meeting.setMercuryListener);
|
2454
2518
|
assert.calledOnce(fakeMediaConnection.initiateOffer);
|
2455
2519
|
assert.equal(meeting.allowMediaInLobby, allowMediaInLobby);
|
2520
|
+
assert.calledOnce(webex.meetings.reachability.stopReachability);
|
2456
2521
|
};
|
2457
2522
|
|
2458
2523
|
it('should attach the media and return promise', async () => {
|
@@ -2471,6 +2536,61 @@ describe('plugin-meetings', () => {
|
|
2471
2536
|
checkWorking();
|
2472
2537
|
});
|
2473
2538
|
|
2539
|
+
it('should upload logs periodically', async () => {
|
2540
|
+
const clock = sinon.useFakeTimers();
|
2541
|
+
|
2542
|
+
meeting.roap.doTurnDiscovery = sinon
|
2543
|
+
.stub()
|
2544
|
+
.resolves({turnServerInfo: undefined, turnDiscoverySkippedReason: undefined});
|
2545
|
+
|
2546
|
+
let logUploadCounter = 0;
|
2547
|
+
|
2548
|
+
TriggerProxy.trigger.callsFake((meetingObject, options, event) => {
|
2549
|
+
if (
|
2550
|
+
meetingObject === meeting &&
|
2551
|
+
options.file === 'meeting/index' &&
|
2552
|
+
options.function === 'uploadLogs' &&
|
2553
|
+
event === 'REQUEST_UPLOAD_LOGS'
|
2554
|
+
) {
|
2555
|
+
logUploadCounter += 1;
|
2556
|
+
}
|
2557
|
+
});
|
2558
|
+
|
2559
|
+
meeting.config.logUploadIntervalMultiplicationFactor = 1;
|
2560
|
+
meeting.meetingState = 'ACTIVE';
|
2561
|
+
|
2562
|
+
await meeting.addMedia({
|
2563
|
+
mediaSettings: {},
|
2564
|
+
});
|
2565
|
+
|
2566
|
+
const checkLogCounter = (delayInMinutes, expectedCounter) => {
|
2567
|
+
const delayInMilliseconds = delayInMinutes * 60 * 1000;
|
2568
|
+
|
2569
|
+
// first check that the counter is not increased just before the delay
|
2570
|
+
clock.tick(delayInMilliseconds - 50);
|
2571
|
+
assert.equal(logUploadCounter, expectedCounter - 1);
|
2572
|
+
|
2573
|
+
// and now check that it has reached expected value after the delay
|
2574
|
+
clock.tick(50);
|
2575
|
+
assert.equal(logUploadCounter, expectedCounter);
|
2576
|
+
};
|
2577
|
+
|
2578
|
+
checkLogCounter(0.1, 1);
|
2579
|
+
checkLogCounter(15, 2);
|
2580
|
+
checkLogCounter(30, 3);
|
2581
|
+
checkLogCounter(60, 4);
|
2582
|
+
checkLogCounter(60, 5);
|
2583
|
+
|
2584
|
+
// simulate media connection being removed -> 1 more upload should happen, but nothing more afterwards
|
2585
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
2586
|
+
checkLogCounter(60, 6);
|
2587
|
+
|
2588
|
+
clock.tick(120 * 1000 * 60);
|
2589
|
+
assert.equal(logUploadCounter, 6);
|
2590
|
+
|
2591
|
+
clock.restore();
|
2592
|
+
});
|
2593
|
+
|
2474
2594
|
it('should attach the media and return promise when in the lobby if allowMediaInLobby is set', async () => {
|
2475
2595
|
meeting.roap.doTurnDiscovery = sinon
|
2476
2596
|
.stub()
|
@@ -2593,6 +2713,7 @@ describe('plugin-meetings', () => {
|
|
2593
2713
|
webex.meetings.reachability = {
|
2594
2714
|
isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
|
2595
2715
|
getReachabilityMetrics: sinon.stub().resolves(),
|
2716
|
+
stopReachability: sinon.stub(),
|
2596
2717
|
};
|
2597
2718
|
const MOCK_CLIENT_ERROR_CODE = 2004;
|
2598
2719
|
const generateClientErrorCodeForIceFailureStub = sinon
|
@@ -2801,6 +2922,7 @@ describe('plugin-meetings', () => {
|
|
2801
2922
|
.onCall(2)
|
2802
2923
|
.resolves(false),
|
2803
2924
|
getReachabilityMetrics: sinon.stub().resolves({}),
|
2925
|
+
stopReachability: sinon.stub(),
|
2804
2926
|
};
|
2805
2927
|
const getErrorPayloadForClientErrorCodeStub =
|
2806
2928
|
(webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
|
@@ -3095,6 +3217,7 @@ describe('plugin-meetings', () => {
|
|
3095
3217
|
someReachabilityMetric1: 'some value1',
|
3096
3218
|
someReachabilityMetric2: 'some value2',
|
3097
3219
|
}),
|
3220
|
+
stopReachability: sinon.stub(),
|
3098
3221
|
};
|
3099
3222
|
meeting.iceCandidatesCount = 3;
|
3100
3223
|
meeting.iceCandidateErrors.set('701_error', 3);
|
@@ -3424,6 +3547,51 @@ describe('plugin-meetings', () => {
|
|
3424
3547
|
});
|
3425
3548
|
});
|
3426
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
|
+
|
3427
3595
|
it('calls submitMQE correctly', async () => {
|
3428
3596
|
const fakeData = {intervalMetadata: {bla: 'bla'}, networkType: 'wifi'};
|
3429
3597
|
|
@@ -3501,14 +3669,6 @@ describe('plugin-meetings', () => {
|
|
3501
3669
|
});
|
3502
3670
|
});
|
3503
3671
|
|
3504
|
-
it('succeeds even if getDevices() throws', async () => {
|
3505
|
-
meeting.meetingState = 'ACTIVE';
|
3506
|
-
|
3507
|
-
sinon.stub(InternalMediaCoreModule, 'getDevices').rejects(new Error('fake error'));
|
3508
|
-
|
3509
|
-
await meeting.addMedia();
|
3510
|
-
});
|
3511
|
-
|
3512
3672
|
describe('CA ice failures checks', () => {
|
3513
3673
|
[
|
3514
3674
|
{
|
@@ -3562,6 +3722,7 @@ describe('plugin-meetings', () => {
|
|
3562
3722
|
|
3563
3723
|
webex.meetings.reachability = {
|
3564
3724
|
isWebexMediaBackendUnreachable: sinon.stub().resolves(unreachable || false),
|
3725
|
+
stopReachability: sinon.stub(),
|
3565
3726
|
};
|
3566
3727
|
|
3567
3728
|
const generateClientErrorCodeForIceFailureStub = sinon
|
@@ -3650,6 +3811,73 @@ describe('plugin-meetings', () => {
|
|
3650
3811
|
});
|
3651
3812
|
});
|
3652
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
|
+
|
3653
3881
|
/* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
|
3654
3882
|
They mock the @webex/internal-media-core and sending of /media http requests to Locus.
|
3655
3883
|
Their main purpose is to test that we send the right http requests to Locus and make right calls
|
@@ -3692,6 +3920,12 @@ describe('plugin-meetings', () => {
|
|
3692
3920
|
meeting.setMercuryListener = sinon.stub();
|
3693
3921
|
meeting.locusInfo.onFullLocus = sinon.stub();
|
3694
3922
|
meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
|
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'});
|
3695
3929
|
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
3696
3930
|
turnServerInfo: {
|
3697
3931
|
url: 'turns:turn-server-url:443?transport=tcp',
|
@@ -3772,6 +4006,7 @@ describe('plugin-meetings', () => {
|
|
3772
4006
|
initiateOffer: sinon.stub().resolves({}),
|
3773
4007
|
update: sinon.stub().resolves({}),
|
3774
4008
|
on: sinon.stub(),
|
4009
|
+
roapMessageReceived: sinon.stub()
|
3775
4010
|
};
|
3776
4011
|
|
3777
4012
|
fakeMultistreamRoapMediaConnection = {
|
@@ -3858,8 +4093,10 @@ describe('plugin-meetings', () => {
|
|
3858
4093
|
};
|
3859
4094
|
|
3860
4095
|
// simulates a Roap offer being generated by the RoapMediaConnection
|
3861
|
-
const simulateRoapOffer = async () => {
|
3862
|
-
|
4096
|
+
const simulateRoapOffer = async (stubWaitingForAnswer = true) => {
|
4097
|
+
if (stubWaitingForAnswer) {
|
4098
|
+
meeting.deferSDPAnswer = {resolve: sinon.stub()};
|
4099
|
+
}
|
3863
4100
|
const roapListener = getRoapListener();
|
3864
4101
|
|
3865
4102
|
await roapListener({roapMessage: roapOfferMessage});
|
@@ -3877,6 +4114,15 @@ describe('plugin-meetings', () => {
|
|
3877
4114
|
const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
|
3878
4115
|
const {sdp, seq, tieBreaker} = roapOfferMessage;
|
3879
4116
|
|
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
|
+
);
|
4125
|
+
|
3880
4126
|
assert.calledWith(locusMediaRequestStub, {
|
3881
4127
|
method: 'PUT',
|
3882
4128
|
uri: `${meeting.selfUrl}/media`,
|
@@ -3890,14 +4136,12 @@ describe('plugin-meetings', () => {
|
|
3890
4136
|
correlationId: meeting.correlationId,
|
3891
4137
|
localMedias: [
|
3892
4138
|
{
|
3893
|
-
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}","headers":["includeAnswerInHttpResponse","noOkInTransaction"]}}`,
|
4139
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}","headers":["includeAnswerInHttpResponse","noOkInTransaction"]},"reachability":{"id":"fake reachability"}}`,
|
3894
4140
|
mediaId: 'fake media id',
|
3895
4141
|
},
|
3896
4142
|
],
|
3897
4143
|
clientMediaPreferences: {
|
3898
|
-
|
3899
|
-
joinCookie: undefined,
|
3900
|
-
ipver: 0,
|
4144
|
+
id: 'fake clientMediaPreferences',
|
3901
4145
|
},
|
3902
4146
|
},
|
3903
4147
|
});
|
@@ -3918,13 +4162,11 @@ describe('plugin-meetings', () => {
|
|
3918
4162
|
},
|
3919
4163
|
correlationId: meeting.correlationId,
|
3920
4164
|
clientMediaPreferences: {
|
3921
|
-
|
3922
|
-
ipver: undefined,
|
3923
|
-
joinCookie: undefined,
|
4165
|
+
id: 'fake clientMediaPreferences',
|
3924
4166
|
},
|
3925
4167
|
localMedias: [
|
3926
4168
|
{
|
3927
|
-
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OK","version":"2","seq":"${seq}"}}`,
|
4169
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OK","version":"2","seq":"${seq}"},"reachability":{"id":"fake reachability"}}`,
|
3928
4170
|
mediaId: 'fake media id',
|
3929
4171
|
},
|
3930
4172
|
],
|
@@ -3950,10 +4192,6 @@ describe('plugin-meetings', () => {
|
|
3950
4192
|
mediaId: 'fake media id',
|
3951
4193
|
},
|
3952
4194
|
],
|
3953
|
-
clientMediaPreferences: {
|
3954
|
-
preferTranscoding: !meeting.isMultistream,
|
3955
|
-
ipver: undefined,
|
3956
|
-
},
|
3957
4195
|
respOnlySdp: true,
|
3958
4196
|
usingResource: null,
|
3959
4197
|
},
|
@@ -3967,8 +4205,9 @@ describe('plugin-meetings', () => {
|
|
3967
4205
|
remoteQualityLevel,
|
3968
4206
|
expectedDebugId,
|
3969
4207
|
meetingId,
|
4208
|
+
expectMultistream = isMultistream,
|
3970
4209
|
}) => {
|
3971
|
-
if (
|
4210
|
+
if (expectMultistream) {
|
3972
4211
|
const {iceServers} = mediaConnectionConfig;
|
3973
4212
|
|
3974
4213
|
assert.calledOnceWithMatch(
|
@@ -4128,7 +4367,6 @@ describe('plugin-meetings', () => {
|
|
4128
4367
|
});
|
4129
4368
|
|
4130
4369
|
it('addMedia() works correctly when media is enabled with streams to publish', async () => {
|
4131
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
4132
4370
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
4133
4371
|
await simulateRoapOffer();
|
4134
4372
|
await simulateRoapOk();
|
@@ -4159,12 +4397,9 @@ describe('plugin-meetings', () => {
|
|
4159
4397
|
|
4160
4398
|
// and that these were the only /media requests that were sent
|
4161
4399
|
assert.calledTwice(locusMediaRequestStub);
|
4162
|
-
|
4163
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
4164
4400
|
});
|
4165
4401
|
|
4166
4402
|
it('addMedia() works correctly when media is enabled with streams to publish and stream is user muted', async () => {
|
4167
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
4168
4403
|
fakeMicrophoneStream.userMuted = true;
|
4169
4404
|
|
4170
4405
|
await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
|
@@ -4196,7 +4431,6 @@ describe('plugin-meetings', () => {
|
|
4196
4431
|
|
4197
4432
|
// and that these were the only /media requests that were sent
|
4198
4433
|
assert.calledTwice(locusMediaRequestStub);
|
4199
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
4200
4434
|
});
|
4201
4435
|
|
4202
4436
|
it('addMedia() works correctly when media is enabled with tracks to publish and track is ended', async () => {
|
@@ -4268,7 +4502,6 @@ describe('plugin-meetings', () => {
|
|
4268
4502
|
});
|
4269
4503
|
|
4270
4504
|
it('addMedia() works correctly when media is disabled with streams to publish', async () => {
|
4271
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
4272
4505
|
await meeting.addMedia({
|
4273
4506
|
localStreams: {microphone: fakeMicrophoneStream},
|
4274
4507
|
audioEnabled: false,
|
@@ -4302,20 +4535,6 @@ describe('plugin-meetings', () => {
|
|
4302
4535
|
|
4303
4536
|
// and that these were the only /media requests that were sent
|
4304
4537
|
assert.calledTwice(locusMediaRequestStub);
|
4305
|
-
assert.calledOnce(handleDeviceLoggingSpy);
|
4306
|
-
});
|
4307
|
-
|
4308
|
-
it('handleDeviceLogging not called when media is disabled', async () => {
|
4309
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
4310
|
-
await meeting.addMedia({
|
4311
|
-
localStreams: {microphone: fakeMicrophoneStream},
|
4312
|
-
audioEnabled: false,
|
4313
|
-
videoEnabled: false,
|
4314
|
-
});
|
4315
|
-
await simulateRoapOffer();
|
4316
|
-
await simulateRoapOk();
|
4317
|
-
|
4318
|
-
assert.notCalled(handleDeviceLoggingSpy);
|
4319
4538
|
});
|
4320
4539
|
|
4321
4540
|
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
@@ -4351,20 +4570,6 @@ describe('plugin-meetings', () => {
|
|
4351
4570
|
assert.calledTwice(locusMediaRequestStub);
|
4352
4571
|
});
|
4353
4572
|
|
4354
|
-
it('addMedia() works correctly when media is disabled with no streams to publish', async () => {
|
4355
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
4356
|
-
await meeting.addMedia({audioEnabled: false});
|
4357
|
-
//calling handleDeviceLogging with audioEnaled as true adn videoEnabled as false
|
4358
|
-
assert.calledWith(handleDeviceLoggingSpy, false, true);
|
4359
|
-
});
|
4360
|
-
|
4361
|
-
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
4362
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
4363
|
-
await meeting.addMedia({videoEnabled: false});
|
4364
|
-
//calling handleDeviceLogging audioEnabled as true videoEnabled as false
|
4365
|
-
assert.calledWith(handleDeviceLoggingSpy, true, false);
|
4366
|
-
});
|
4367
|
-
|
4368
4573
|
it('addMedia() works correctly when video is disabled with no streams to publish', async () => {
|
4369
4574
|
await meeting.addMedia({videoEnabled: false});
|
4370
4575
|
await simulateRoapOffer();
|
@@ -4431,13 +4636,6 @@ describe('plugin-meetings', () => {
|
|
4431
4636
|
assert.calledTwice(locusMediaRequestStub);
|
4432
4637
|
});
|
4433
4638
|
|
4434
|
-
it('addMedia() works correctly when both shareAudio and shareVideo is disabled with no streams publish', async () => {
|
4435
|
-
const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging');
|
4436
|
-
await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false});
|
4437
|
-
//calling handleDeviceLogging with audioEnabled true and videoEnabled as true
|
4438
|
-
assert.calledWith(handleDeviceLoggingSpy, true, true);
|
4439
|
-
});
|
4440
|
-
|
4441
4639
|
describe('publishStreams()/unpublishStreams() calls', () => {
|
4442
4640
|
[
|
4443
4641
|
{mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
|
@@ -4833,6 +5031,211 @@ describe('plugin-meetings', () => {
|
|
4833
5031
|
assert.notCalled(fakeRoapMediaConnection.update);
|
4834
5032
|
})
|
4835
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
|
+
}
|
4836
5239
|
})
|
4837
5240
|
);
|
4838
5241
|
|
@@ -4910,6 +5313,11 @@ describe('plugin-meetings', () => {
|
|
4910
5313
|
meeting.logger.error = sinon.stub().returns(true);
|
4911
5314
|
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
4912
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();
|
4913
5321
|
|
4914
5322
|
// A meeting needs to be joined to leave
|
4915
5323
|
meeting.meetingState = 'ACTIVE';
|
@@ -4930,6 +5338,9 @@ describe('plugin-meetings', () => {
|
|
4930
5338
|
assert.calledOnce(meeting.closePeerConnections);
|
4931
5339
|
assert.calledOnce(meeting.unsetRemoteStreams);
|
4932
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);
|
4933
5344
|
});
|
4934
5345
|
|
4935
5346
|
it('should reset call diagnostic latencies correctly', async () => {
|
@@ -5917,6 +6328,38 @@ describe('plugin-meetings', () => {
|
|
5917
6328
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
5918
6329
|
});
|
5919
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
|
+
|
5920
6363
|
it('handles meetingInfoProvider policy error', async () => {
|
5921
6364
|
meeting.destination = FAKE_DESTINATION;
|
5922
6365
|
meeting.destinationType = FAKE_TYPE;
|
@@ -6284,29 +6727,74 @@ describe('plugin-meetings', () => {
|
|
6284
6727
|
assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
|
6285
6728
|
});
|
6286
6729
|
|
6287
|
-
it('handles
|
6730
|
+
it('handles MeetingInfoV2JoinWebinarError webinar need registration', async () => {
|
6288
6731
|
meeting.destination = FAKE_DESTINATION;
|
6289
6732
|
meeting.destinationType = FAKE_TYPE;
|
6290
6733
|
meeting.attrs.meetingInfoProvider = {
|
6291
6734
|
fetchMeetingInfo: sinon
|
6292
6735
|
.stub()
|
6293
6736
|
.throws(
|
6294
|
-
new
|
6737
|
+
new MeetingInfoV2JoinWebinarError(403021, FAKE_MEETING_INFO, 'a message')
|
6295
6738
|
),
|
6296
6739
|
};
|
6297
6740
|
|
6298
6741
|
await assert.isRejected(
|
6299
6742
|
meeting.fetchMeetingInfo({sendCAevents: true}),
|
6300
|
-
|
6743
|
+
JoinWebinarError
|
6301
6744
|
);
|
6302
6745
|
|
6303
6746
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
6304
|
-
assert.equal(meeting.meetingInfoFailureCode, 403021);
|
6305
6747
|
assert.equal(
|
6306
6748
|
meeting.meetingInfoFailureReason,
|
6307
6749
|
MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION
|
6308
6750
|
);
|
6309
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_REGISTRATION_ID
|
6796
|
+
);
|
6797
|
+
});
|
6310
6798
|
});
|
6311
6799
|
|
6312
6800
|
describe('#refreshPermissionToken', () => {
|
@@ -6364,7 +6852,8 @@ describe('plugin-meetings', () => {
|
|
6364
6852
|
'fake-installed-org-id',
|
6365
6853
|
'locus-id',
|
6366
6854
|
{extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
|
6367
|
-
{meetingId: meeting.id, sendCAevents: true}
|
6855
|
+
{meetingId: meeting.id, sendCAevents: true},
|
6856
|
+
null
|
6368
6857
|
);
|
6369
6858
|
assert.deepEqual(meeting.meetingInfo, {
|
6370
6859
|
...FAKE_MEETING_INFO,
|
@@ -6409,7 +6898,8 @@ describe('plugin-meetings', () => {
|
|
6409
6898
|
'fake-installed-org-id',
|
6410
6899
|
'locus-id',
|
6411
6900
|
{extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
|
6412
|
-
{meetingId: meeting.id, sendCAevents: true}
|
6901
|
+
{meetingId: meeting.id, sendCAevents: true},
|
6902
|
+
null
|
6413
6903
|
);
|
6414
6904
|
assert.deepEqual(meeting.meetingInfo, {
|
6415
6905
|
...FAKE_MEETING_INFO,
|
@@ -6463,7 +6953,8 @@ describe('plugin-meetings', () => {
|
|
6463
6953
|
extraParam1: 'value1',
|
6464
6954
|
permissionToken: FAKE_PERMISSION_TOKEN,
|
6465
6955
|
},
|
6466
|
-
{meetingId: meeting.id, sendCAevents: true}
|
6956
|
+
{meetingId: meeting.id, sendCAevents: true},
|
6957
|
+
null
|
6467
6958
|
);
|
6468
6959
|
assert.deepEqual(meeting.meetingInfo, {
|
6469
6960
|
...FAKE_MEETING_INFO,
|
@@ -6767,6 +7258,9 @@ describe('plugin-meetings', () => {
|
|
6767
7258
|
meeting.transcription = {};
|
6768
7259
|
meeting.stopTranscription = sinon.stub();
|
6769
7260
|
|
7261
|
+
meeting.annotation.deregisterEvents = sinon.stub();
|
7262
|
+
webex.internal.llm.off = sinon.stub();
|
7263
|
+
|
6770
7264
|
// A meeting needs to be joined to end
|
6771
7265
|
meeting.meetingState = 'ACTIVE';
|
6772
7266
|
meeting.state = 'JOINED';
|
@@ -6787,6 +7281,9 @@ describe('plugin-meetings', () => {
|
|
6787
7281
|
assert.calledOnce(meeting?.unsetRemoteStreams);
|
6788
7282
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
6789
7283
|
assert.calledOnce(meeting?.stopTranscription);
|
7284
|
+
|
7285
|
+
assert.called(meeting.annotation.deregisterEvents);
|
7286
|
+
assert.calledWith(webex.internal.llm.off, 'event:relay.event', meeting.processRelayEvent);
|
6790
7287
|
});
|
6791
7288
|
});
|
6792
7289
|
|
@@ -7769,7 +8266,9 @@ describe('plugin-meetings', () => {
|
|
7769
8266
|
});
|
7770
8267
|
|
7771
8268
|
it('should collect ice candidates', () => {
|
7772
|
-
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
|
8269
|
+
eventListeners[MediaConnectionEventNames.ICE_CANDIDATE]({
|
8270
|
+
candidate: {candidate: 'candidate'},
|
8271
|
+
});
|
7773
8272
|
|
7774
8273
|
assert.equal(meeting.iceCandidatesCount, 1);
|
7775
8274
|
});
|
@@ -8075,10 +8574,10 @@ describe('plugin-meetings', () => {
|
|
8075
8574
|
meeting.statsAnalyzer.stopAnalyzer = sinon.stub().resolves();
|
8076
8575
|
meeting.reconnectionManager = {
|
8077
8576
|
reconnect: sinon.stub().resolves(),
|
8078
|
-
resetReconnectionTimer: () => {}
|
8577
|
+
resetReconnectionTimer: () => {},
|
8079
8578
|
};
|
8080
8579
|
meeting.currentMediaStatus = {
|
8081
|
-
video: true
|
8580
|
+
video: true,
|
8082
8581
|
};
|
8083
8582
|
|
8084
8583
|
await mockFailedEvent();
|
@@ -8360,8 +8859,7 @@ describe('plugin-meetings', () => {
|
|
8360
8859
|
assert.calledWith(meeting.roapMessageReceived, fakeAnswer);
|
8361
8860
|
});
|
8362
8861
|
|
8363
|
-
|
8364
|
-
const fakeError = new Error('fake error');
|
8862
|
+
const runOfferSendingFailureTest = async (fakeError, canProceed, expectedErrorCode) => {
|
8365
8863
|
const clock = sinon.useFakeTimers();
|
8366
8864
|
sinon.spy(clock, 'clearTimeout');
|
8367
8865
|
meeting.deferSDPAnswer = {reject: sinon.stub()};
|
@@ -8399,19 +8897,31 @@ describe('plugin-meetings', () => {
|
|
8399
8897
|
assert.equal(meeting.sdpResponseTimer, undefined);
|
8400
8898
|
|
8401
8899
|
assert.calledOnceWithExactly(getErrorPayloadForClientErrorCodeStub, {
|
8402
|
-
clientErrorCode:
|
8900
|
+
clientErrorCode: expectedErrorCode,
|
8403
8901
|
});
|
8404
8902
|
assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
|
8405
8903
|
name: 'client.media-engine.remote-sdp-received',
|
8406
8904
|
payload: {
|
8407
|
-
canProceed
|
8408
|
-
errors: [{errorCode:
|
8905
|
+
canProceed,
|
8906
|
+
errors: [{errorCode: expectedErrorCode, fatal: true}],
|
8409
8907
|
},
|
8410
8908
|
options: {
|
8411
8909
|
meetingId: meeting.id,
|
8412
8910
|
rawError: fakeError,
|
8413
8911
|
},
|
8414
8912
|
});
|
8913
|
+
};
|
8914
|
+
|
8915
|
+
it('handles OFFER message correctly when request fails', async () => {
|
8916
|
+
const fakeError = new Error('fake error');
|
8917
|
+
|
8918
|
+
await runOfferSendingFailureTest(fakeError, false, 2007);
|
8919
|
+
});
|
8920
|
+
|
8921
|
+
it('handles OFFER message correctly when we get a non-homer answer', async () => {
|
8922
|
+
const fakeError = new MultistreamNotSupportedError();
|
8923
|
+
|
8924
|
+
await runOfferSendingFailureTest(fakeError, true, 2012);
|
8415
8925
|
});
|
8416
8926
|
|
8417
8927
|
it('handles ANSWER message correctly', () => {
|
@@ -8614,6 +9124,7 @@ describe('plugin-meetings', () => {
|
|
8614
9124
|
});
|
8615
9125
|
});
|
8616
9126
|
});
|
9127
|
+
|
8617
9128
|
describe('#setUpLocusInfoSelfListener', () => {
|
8618
9129
|
it('listens to the self unadmitted guest event', (done) => {
|
8619
9130
|
meeting.startKeepAlive = sinon.stub();
|
@@ -8629,6 +9140,13 @@ describe('plugin-meetings', () => {
|
|
8629
9140
|
{payload: test1}
|
8630
9141
|
);
|
8631
9142
|
assert.calledOnce(meeting.updateLLMConnection);
|
9143
|
+
assert.calledOnceWithExactly(
|
9144
|
+
Metrics.sendBehavioralMetric,
|
9145
|
+
BEHAVIORAL_METRICS.GUEST_ENTERED_LOBBY,
|
9146
|
+
{
|
9147
|
+
correlation_id: meeting.correlationId,
|
9148
|
+
}
|
9149
|
+
);
|
8632
9150
|
done();
|
8633
9151
|
});
|
8634
9152
|
it('listens to the self admitted guest event', (done) => {
|
@@ -8650,6 +9168,13 @@ describe('plugin-meetings', () => {
|
|
8650
9168
|
assert.calledOnce(meeting.updateLLMConnection);
|
8651
9169
|
assert.calledOnceWithExactly(meeting.rtcMetrics.sendNextMetrics);
|
8652
9170
|
|
9171
|
+
assert.calledOnceWithExactly(
|
9172
|
+
Metrics.sendBehavioralMetric,
|
9173
|
+
BEHAVIORAL_METRICS.GUEST_EXITED_LOBBY,
|
9174
|
+
{
|
9175
|
+
correlation_id: meeting.correlationId,
|
9176
|
+
}
|
9177
|
+
);
|
8653
9178
|
done();
|
8654
9179
|
});
|
8655
9180
|
|
@@ -8694,6 +9219,27 @@ describe('plugin-meetings', () => {
|
|
8694
9219
|
);
|
8695
9220
|
});
|
8696
9221
|
|
9222
|
+
it('listens to the brb state changed event', () => {
|
9223
|
+
const assertBrb = (enabled) => {
|
9224
|
+
meeting.brbState = createBrbState(meeting, false);
|
9225
|
+
meeting.locusInfo.emit(
|
9226
|
+
{ function: 'test', file: 'test' },
|
9227
|
+
LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
|
9228
|
+
{ brb: { enabled } },
|
9229
|
+
)
|
9230
|
+
assert.calledWithExactly(
|
9231
|
+
TriggerProxy.trigger,
|
9232
|
+
meeting,
|
9233
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
9234
|
+
EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
|
9235
|
+
{ payload: { brb: { enabled } } },
|
9236
|
+
);
|
9237
|
+
}
|
9238
|
+
|
9239
|
+
assertBrb(true);
|
9240
|
+
assertBrb(false);
|
9241
|
+
})
|
9242
|
+
|
8697
9243
|
it('listens to the interpretation changed event', () => {
|
8698
9244
|
meeting.simultaneousInterpretation.updateSelfInterpretation = sinon.stub();
|
8699
9245
|
|
@@ -8982,6 +9528,8 @@ describe('plugin-meetings', () => {
|
|
8982
9528
|
});
|
8983
9529
|
|
8984
9530
|
it('listens to MEETING_CONTROLS_PRACTICE_SESSION_STATUS_UPDATED', async () => {
|
9531
|
+
meeting.webinar.updatePracticeSessionStatus = sinon.stub();
|
9532
|
+
|
8985
9533
|
const state = {example: 'value'};
|
8986
9534
|
|
8987
9535
|
await meeting.locusInfo.emitScoped(
|
@@ -8990,6 +9538,7 @@ describe('plugin-meetings', () => {
|
|
8990
9538
|
{state}
|
8991
9539
|
);
|
8992
9540
|
|
9541
|
+
assert.calledOnceWithExactly(meeting.webinar.updatePracticeSessionStatus, state);
|
8993
9542
|
assert.calledWith(
|
8994
9543
|
TriggerProxy.trigger,
|
8995
9544
|
meeting,
|
@@ -9472,15 +10021,44 @@ describe('plugin-meetings', () => {
|
|
9472
10021
|
describe('#closePeerConnections', () => {
|
9473
10022
|
it('should close the webrtc media connection, and return a promise', async () => {
|
9474
10023
|
const setNetworkStatusSpy = sinon.spy(meeting, 'setNetworkStatus');
|
9475
|
-
|
10024
|
+
const fakeWebrtcMediaConnection = {close: sinon.stub()};
|
10025
|
+
meeting.mediaProperties.webrtcMediaConnection = fakeWebrtcMediaConnection;
|
10026
|
+
|
10027
|
+
meeting.audio = {id: 'fakeAudioMuteState'};
|
10028
|
+
meeting.video = {id: 'fakeVideoMuteState'};
|
10029
|
+
|
9476
10030
|
const pcs = meeting.closePeerConnections();
|
9477
10031
|
|
9478
10032
|
assert.exists(pcs.then);
|
9479
10033
|
await pcs;
|
9480
|
-
assert.calledOnce(
|
10034
|
+
assert.calledOnce(fakeWebrtcMediaConnection.close);
|
10035
|
+
assert.calledOnceWithExactly(setNetworkStatusSpy, undefined);
|
10036
|
+
assert.equal(meeting.audio, null);
|
10037
|
+
assert.equal(meeting.video, null);
|
10038
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection, null);
|
10039
|
+
});
|
10040
|
+
|
10041
|
+
it('should close the webrtc media connection, but keep audio and video props unchanged if called with resetMuteStates=false', async () => {
|
10042
|
+
const setNetworkStatusSpy = sinon.spy(meeting, 'setNetworkStatus');
|
10043
|
+
const fakeWebrtcMediaConnection = {close: sinon.stub()};
|
10044
|
+
meeting.mediaProperties.webrtcMediaConnection = fakeWebrtcMediaConnection;
|
10045
|
+
|
10046
|
+
const fakeAudio = {id: 'fakeAudioMuteState'};
|
10047
|
+
const fakeVideo = {id: 'fakeVideoMuteState'};
|
10048
|
+
|
10049
|
+
meeting.audio = fakeAudio;
|
10050
|
+
meeting.video = fakeVideo;
|
10051
|
+
|
10052
|
+
await meeting.closePeerConnections(false);
|
10053
|
+
|
10054
|
+
assert.calledOnce(fakeWebrtcMediaConnection.close);
|
9481
10055
|
assert.calledOnceWithExactly(setNetworkStatusSpy, undefined);
|
10056
|
+
assert.equal(meeting.audio, fakeAudio);
|
10057
|
+
assert.equal(meeting.video, fakeVideo);
|
10058
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection, null);
|
9482
10059
|
});
|
9483
10060
|
});
|
10061
|
+
|
9484
10062
|
describe('#unsetPeerConnections', () => {
|
9485
10063
|
it('should unset the peer connections', () => {
|
9486
10064
|
meeting.mediaProperties.unsetPeerConnection = sinon.stub().returns(true);
|
@@ -10609,6 +11187,7 @@ describe('plugin-meetings', () => {
|
|
10609
11187
|
meeting.webex.internal.llm.on = sinon.stub();
|
10610
11188
|
meeting.webex.internal.llm.off = sinon.stub();
|
10611
11189
|
meeting.processRelayEvent = sinon.stub();
|
11190
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(false);
|
10612
11191
|
});
|
10613
11192
|
|
10614
11193
|
it('does not connect if the call is not joined yet', async () => {
|
@@ -10740,6 +11319,19 @@ describe('plugin-meetings', () => {
|
|
10740
11319
|
meeting.processRelayEvent
|
10741
11320
|
);
|
10742
11321
|
});
|
11322
|
+
|
11323
|
+
|
11324
|
+
it('connect ps data channel if ps started in webinar', async () => {
|
11325
|
+
meeting.joinedWith = {state: 'JOINED'};
|
11326
|
+
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url', practiceSessionDatachannelUrl: 'a ps datachannel url'}};
|
11327
|
+
meeting.webinar.isJoinPracticeSessionDataChannel = sinon.stub().returns(true);
|
11328
|
+
await meeting.updateLLMConnection();
|
11329
|
+
|
11330
|
+
assert.notCalled(webex.internal.llm.disconnectLLM);
|
11331
|
+
assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a ps datachannel url');
|
11332
|
+
|
11333
|
+
});
|
11334
|
+
|
10743
11335
|
});
|
10744
11336
|
|
10745
11337
|
describe('#setLocus', () => {
|
@@ -10931,6 +11523,7 @@ describe('plugin-meetings', () => {
|
|
10931
11523
|
beforeEach(() => {
|
10932
11524
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
10933
11525
|
meeting.deviceUrl = 'my-web-url';
|
11526
|
+
meeting.locusInfo.info = {isWebinar: false};
|
10934
11527
|
});
|
10935
11528
|
|
10936
11529
|
const USER_IDS = {
|
@@ -11156,13 +11749,24 @@ describe('plugin-meetings', () => {
|
|
11156
11749
|
|
11157
11750
|
activeSharingId.whiteboard = beneficiaryId;
|
11158
11751
|
|
11159
|
-
eventTrigger.share.push({
|
11752
|
+
eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
|
11753
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
11754
|
+
functionName: 'remoteShare',
|
11755
|
+
eventPayload: {
|
11756
|
+
memberId: null,
|
11757
|
+
url,
|
11758
|
+
shareInstanceId,
|
11759
|
+
annotationInfo: undefined,
|
11760
|
+
resourceType: undefined,
|
11761
|
+
},
|
11762
|
+
} : {
|
11160
11763
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
11161
11764
|
functionName: 'startWhiteboardShare',
|
11162
11765
|
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
11163
11766
|
});
|
11164
11767
|
|
11165
|
-
shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
11768
|
+
shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
11769
|
+
|
11166
11770
|
}
|
11167
11771
|
|
11168
11772
|
if (eventTrigger.member) {
|
@@ -11194,13 +11798,24 @@ describe('plugin-meetings', () => {
|
|
11194
11798
|
newPayload.current.content.disposition = FLOOR_ACTION.ACCEPTED;
|
11195
11799
|
newPayload.current.content.beneficiaryId = otherBeneficiaryId;
|
11196
11800
|
|
11197
|
-
eventTrigger.share.push({
|
11801
|
+
eventTrigger.share.push(meeting.webinar.selfIsAttendee ? {
|
11802
|
+
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
11803
|
+
functionName: 'remoteShare',
|
11804
|
+
eventPayload: {
|
11805
|
+
memberId: null,
|
11806
|
+
url,
|
11807
|
+
shareInstanceId,
|
11808
|
+
annotationInfo: undefined,
|
11809
|
+
resourceType: undefined,
|
11810
|
+
},
|
11811
|
+
} : {
|
11198
11812
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_WHITEBOARD,
|
11199
11813
|
functionName: 'startWhiteboardShare',
|
11200
11814
|
eventPayload: {resourceUrl, memberId: beneficiaryId},
|
11201
11815
|
});
|
11202
11816
|
|
11203
|
-
shareStatus = SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
11817
|
+
shareStatus = meeting.webinar.selfIsAttendee ? SHARE_STATUS.REMOTE_SHARE_ACTIVE : SHARE_STATUS.WHITEBOARD_SHARE_ACTIVE;
|
11818
|
+
|
11204
11819
|
} else {
|
11205
11820
|
eventTrigger.share.push({
|
11206
11821
|
eventName: EVENT_TRIGGERS.MEETING_STOPPED_SHARING_WHITEBOARD,
|
@@ -11327,6 +11942,38 @@ describe('plugin-meetings', () => {
|
|
11327
11942
|
assert.exists(meeting.setUpLocusMediaSharesListener);
|
11328
11943
|
});
|
11329
11944
|
|
11945
|
+
describe('Whiteboard Share - Webinar Attendee', () => {
|
11946
|
+
it('Scenario #1: Whiteboard sharing as a webinar attendee', () => {
|
11947
|
+
// Set the webinar attendee flag
|
11948
|
+
meeting.webinar = { selfIsAttendee: true };
|
11949
|
+
meeting.locusInfo.info.isWebinar = true;
|
11950
|
+
|
11951
|
+
// Step 1: Start sharing whiteboard A
|
11952
|
+
const data1 = generateData(
|
11953
|
+
blankPayload, // Initial payload
|
11954
|
+
true, // isGranting: Granting share
|
11955
|
+
false, // isContent: Whiteboard (not content)
|
11956
|
+
USER_IDS.REMOTE_A, // Beneficiary ID: Remote user A
|
11957
|
+
RESOURCE_URLS.WHITEBOARD_A // Resource URL: Whiteboard A
|
11958
|
+
);
|
11959
|
+
|
11960
|
+
// Step 2: Stop sharing whiteboard A
|
11961
|
+
const data2 = generateData(
|
11962
|
+
data1.payload, // Updated payload from Step 1
|
11963
|
+
false, // isGranting: Stopping share
|
11964
|
+
false, // isContent: Whiteboard
|
11965
|
+
USER_IDS.REMOTE_A // Beneficiary ID: Remote user A
|
11966
|
+
);
|
11967
|
+
|
11968
|
+
// Validate the payload changes and status updates
|
11969
|
+
payloadTestHelper([data1]);
|
11970
|
+
|
11971
|
+
// Specific assertions for webinar attendee status
|
11972
|
+
assert.equal(meeting.shareStatus, SHARE_STATUS.REMOTE_SHARE_ACTIVE);
|
11973
|
+
});
|
11974
|
+
});
|
11975
|
+
|
11976
|
+
|
11330
11977
|
describe('Whiteboard A --> Whiteboard B', () => {
|
11331
11978
|
it('Scenario #1: you share both whiteboards', () => {
|
11332
11979
|
const data1 = generateData(
|
@@ -12002,9 +12649,12 @@ describe('plugin-meetings', () => {
|
|
12002
12649
|
it('startKeepAlive starts the keep alive', async () => {
|
12003
12650
|
meeting.meetingRequest.keepAlive = sinon.stub().returns(Promise.resolve());
|
12004
12651
|
|
12652
|
+
const keepAliveUrl1 = 'keep.alive.url1';
|
12653
|
+
const keepAliveUrl2 = 'keep.alive.url2';
|
12654
|
+
|
12005
12655
|
assert.isNull(meeting.keepAliveTimerId);
|
12006
12656
|
meeting.joinedWith = {
|
12007
|
-
keepAliveUrl:
|
12657
|
+
keepAliveUrl: keepAliveUrl1,
|
12008
12658
|
keepAliveSecs: defaultKeepAliveSecs,
|
12009
12659
|
};
|
12010
12660
|
meeting.startKeepAlive();
|
@@ -12013,12 +12663,15 @@ describe('plugin-meetings', () => {
|
|
12013
12663
|
assert.notCalled(meeting.meetingRequest.keepAlive);
|
12014
12664
|
await progressTime(defaultExpectedInterval);
|
12015
12665
|
assert.calledOnceWithExactly(meeting.meetingRequest.keepAlive, {
|
12016
|
-
keepAliveUrl:
|
12666
|
+
keepAliveUrl: keepAliveUrl1,
|
12017
12667
|
});
|
12668
|
+
// joinedWith keep alive url might change (when we fallback from multistream to transcoded)
|
12669
|
+
meeting.joinedWith.keepAliveUrl = keepAliveUrl2;
|
12670
|
+
|
12018
12671
|
await progressTime(defaultExpectedInterval);
|
12019
12672
|
assert.calledTwice(meeting.meetingRequest.keepAlive);
|
12020
|
-
assert.
|
12021
|
-
keepAliveUrl:
|
12673
|
+
assert.calledWith(meeting.meetingRequest.keepAlive, {
|
12674
|
+
keepAliveUrl: keepAliveUrl2,
|
12022
12675
|
});
|
12023
12676
|
});
|
12024
12677
|
it('startKeepAlive handles existing keepAliveTimerId', async () => {
|
@@ -12599,7 +13252,7 @@ describe('plugin-meetings', () => {
|
|
12599
13252
|
|
12600
13253
|
describe('#roapMessageReceived', () => {
|
12601
13254
|
it('calls roapMessageReceived on the webrtc media connection', () => {
|
12602
|
-
const fakeMessage = {messageType: '
|
13255
|
+
const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
|
12603
13256
|
|
12604
13257
|
const getMediaServer = sinon.stub(MeetingsUtil, 'getMediaServer').returns('homer');
|
12605
13258
|
|
@@ -12616,5 +13269,96 @@ describe('plugin-meetings', () => {
|
|
12616
13269
|
assert.calledOnceWithExactly(getMediaServer, 'fake sdp');
|
12617
13270
|
assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'homer');
|
12618
13271
|
});
|
13272
|
+
|
13273
|
+
it('throws MultistreamNotSupportedError if we get a non-homer SDP answer', async () => {
|
13274
|
+
const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
|
13275
|
+
|
13276
|
+
meeting.isMultistream = true;
|
13277
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
13278
|
+
roapMessageReceived: sinon.stub(),
|
13279
|
+
};
|
13280
|
+
|
13281
|
+
sinon.stub(MeetingsUtil, 'getMediaServer').returns('linus');
|
13282
|
+
|
13283
|
+
try {
|
13284
|
+
await meeting.roapMessageReceived(fakeMessage);
|
13285
|
+
|
13286
|
+
assert.fail('Expected MultistreamNotSupportedError to be thrown');
|
13287
|
+
} catch(e) {
|
13288
|
+
assert.isTrue(e instanceof MultistreamNotSupportedError);
|
13289
|
+
}
|
13290
|
+
|
13291
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived);
|
13292
|
+
});
|
13293
|
+
|
13294
|
+
it('does not call getMediaServer for a roap message other than ANSWER', async () => {
|
13295
|
+
const fakeMessage = {messageType: 'ERROR', sdp: 'fake sdp'};
|
13296
|
+
|
13297
|
+
meeting.isMultistream = true;
|
13298
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
13299
|
+
roapMessageReceived: sinon.stub(),
|
13300
|
+
};
|
13301
|
+
meeting.mediaProperties.webrtcMediaConnection.mediaServer = 'linus';
|
13302
|
+
|
13303
|
+
const getMediaServerStub = sinon.stub(MeetingsUtil, 'getMediaServer').returns('something');
|
13304
|
+
|
13305
|
+
meeting.roapMessageReceived(fakeMessage);
|
13306
|
+
|
13307
|
+
assert.calledOnceWithExactly(
|
13308
|
+
meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived,
|
13309
|
+
fakeMessage
|
13310
|
+
);
|
13311
|
+
assert.notCalled(getMediaServerStub);
|
13312
|
+
assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'linus'); // check that it hasn't been overwritten
|
13313
|
+
});
|
13314
|
+
});
|
13315
|
+
|
13316
|
+
describe('#verifyRegistrationId', () => {
|
13317
|
+
it('calls fetchMeetingInfo() with the passed registrationId and captcha code', async () => {
|
13318
|
+
// simulate successful case
|
13319
|
+
meeting.fetchMeetingInfo = sinon.stub().resolves();
|
13320
|
+
const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
|
13321
|
+
|
13322
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
13323
|
+
assert.calledWith(
|
13324
|
+
Metrics.sendBehavioralMetric,
|
13325
|
+
BEHAVIORAL_METRICS.VERIFY_REGISTRATION_ID_SUCCESS
|
13326
|
+
);
|
13327
|
+
assert.equal(result.isRegistrationIdValid, true);
|
13328
|
+
assert.equal(result.requiredCaptcha, null);
|
13329
|
+
assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
13330
|
+
assert.calledWith(meeting.fetchMeetingInfo, {
|
13331
|
+
registrationId: 'registrationId',
|
13332
|
+
captchaCode: 'captcha id',
|
13333
|
+
sendCAevents: false,
|
13334
|
+
});
|
13335
|
+
});
|
13336
|
+
it('handles registrationIdError returned by fetchMeetingInfo', async () => {
|
13337
|
+
meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
|
13338
|
+
meeting.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATIONID;
|
13339
|
+
|
13340
|
+
return Promise.reject(new JoinWebinarError());
|
13341
|
+
});
|
13342
|
+
const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
|
13343
|
+
|
13344
|
+
assert.equal(result.isRegistrationIdValid, false);
|
13345
|
+
assert.equal(result.requiredCaptcha, null);
|
13346
|
+
assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATION_ID);
|
13347
|
+
});
|
13348
|
+
it('handles CaptchaError returned by fetchMeetingInfo', async () => {
|
13349
|
+
const FAKE_CAPTCHA = {captchaId: 'some catcha id...'};
|
13350
|
+
|
13351
|
+
meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
|
13352
|
+
meeting.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA;
|
13353
|
+
meeting.requiredCaptcha = FAKE_CAPTCHA;
|
13354
|
+
|
13355
|
+
return Promise.reject(new CaptchaError());
|
13356
|
+
});
|
13357
|
+
const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
|
13358
|
+
|
13359
|
+
assert.equal(result.isRegistrationIdValid, false);
|
13360
|
+
assert.deepEqual(result.requiredCaptcha, FAKE_CAPTCHA);
|
13361
|
+
assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
|
13362
|
+
});
|
12619
13363
|
});
|
12620
13364
|
});
|