@webex/plugin-meetings 3.7.0-next.56 → 3.7.0-next.58

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.
@@ -0,0 +1,54 @@
1
+ import type Meeting from '.';
2
+ import SendSlotManager from '../multistream/sendSlotManager';
3
+ export declare const createBrbState: (meeting: Meeting, enabled: boolean) => BrbState;
4
+ /** The purpose of this class is to manage the local and remote brb state
5
+ * and make sure that the server state always matches the last requested state by the client.
6
+ */
7
+ export declare class BrbState {
8
+ state: {
9
+ client: {
10
+ enabled: boolean;
11
+ };
12
+ server: {
13
+ enabled: boolean;
14
+ };
15
+ syncToServerInProgress: boolean;
16
+ };
17
+ meeting: Meeting;
18
+ /**
19
+ * Constructor
20
+ *
21
+ * @param {Meeting} meeting - the meeting object
22
+ * @param {boolean} enabled - whether the client audio/video is enabled at all
23
+ */
24
+ constructor(meeting: Meeting, enabled: boolean);
25
+ /**
26
+ * Enables/disables brb
27
+ *
28
+ * @param {boolean} enabled
29
+ * @param {SendSlotManager} sendSlotManager
30
+ * @returns {Promise}
31
+ */
32
+ enable(enabled: boolean, sendSlotManager: SendSlotManager): Promise<void>;
33
+ /**
34
+ * Updates the server local and remote brb values so that they match the current client desired state.
35
+ *
36
+ * @param {SendSlotManager} sendSlotManager
37
+ * @returns {Promise}
38
+ */
39
+ private applyClientStateToServer;
40
+ /**
41
+ * Send the local brb state to the server
42
+ *
43
+ * @param {SendSlotManager} sendSlotManager
44
+ * @returns {Promise}
45
+ */
46
+ private sendLocalBrbStateToServer;
47
+ /**
48
+ * This method should be called whenever the server brb state is changed
49
+ *
50
+ * @param {Boolean} [enabled] true if user has brb enabled, false otherwise
51
+ * @returns {undefined}
52
+ */
53
+ handleServerBrbUpdate(enabled?: boolean): void;
54
+ }
@@ -19,6 +19,7 @@ import InMeetingActions from './in-meeting-actions';
19
19
  import RecordingController from '../recording-controller';
20
20
  import ControlsOptionsManager from '../controls-options-manager';
21
21
  import { LocusMediaRequest } from './locusMediaRequest';
22
+ import { BrbState } from './brbState';
22
23
  export type CaptionData = {
23
24
  id: string;
24
25
  isFinal: boolean;
@@ -449,6 +450,7 @@ export default class Meeting extends StatelessWebexPlugin {
449
450
  turnServerUsed: boolean;
450
451
  areVoiceaEventsSetup: boolean;
451
452
  isMoveToInProgress: boolean;
453
+ brbState: BrbState;
452
454
  voiceaListenerCallbacks: object;
453
455
  private addMediaData;
454
456
  private sendSlotManager;
@@ -458,7 +458,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
458
458
  }, _callee7);
459
459
  }))();
460
460
  },
461
- version: "3.7.0-next.56"
461
+ version: "3.7.0-next.58"
462
462
  });
463
463
  var _default = exports.default = Webinar;
464
464
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -43,7 +43,7 @@
43
43
  "@webex/eslint-config-legacy": "0.0.0",
44
44
  "@webex/jest-config-legacy": "0.0.0",
45
45
  "@webex/legacy-tools": "0.0.0",
46
- "@webex/plugin-meetings": "3.7.0-next.56",
46
+ "@webex/plugin-meetings": "3.7.0-next.58",
47
47
  "@webex/plugin-rooms": "3.7.0-next.22",
48
48
  "@webex/test-helper-chai": "3.7.0-next.16",
49
49
  "@webex/test-helper-mocha": "3.7.0-next.16",
@@ -71,7 +71,7 @@
71
71
  "@webex/internal-plugin-metrics": "3.7.0-next.16",
72
72
  "@webex/internal-plugin-support": "3.7.0-next.23",
73
73
  "@webex/internal-plugin-user": "3.7.0-next.16",
74
- "@webex/internal-plugin-voicea": "3.7.0-next.56",
74
+ "@webex/internal-plugin-voicea": "3.7.0-next.58",
75
75
  "@webex/media-helpers": "3.7.0-next.22",
76
76
  "@webex/plugin-people": "3.7.0-next.20",
77
77
  "@webex/plugin-rooms": "3.7.0-next.22",
@@ -92,5 +92,5 @@
92
92
  "//": [
93
93
  "TODO: upgrade jwt-decode when moving to node 18"
94
94
  ],
95
- "version": "3.7.0-next.56"
95
+ "version": "3.7.0-next.58"
96
96
  }
@@ -0,0 +1,169 @@
1
+ import {MediaType} from '@webex/internal-media-core';
2
+ import LoggerProxy from '../common/logs/logger-proxy';
3
+ import type Meeting from '.';
4
+ import SendSlotManager from '../multistream/sendSlotManager';
5
+
6
+ export const createBrbState = (meeting: Meeting, enabled: boolean) => {
7
+ LoggerProxy.logger.info(
8
+ `Meeting:brbState#createBrbState: creating BrbState for meeting id ${meeting?.id}`
9
+ );
10
+
11
+ const brbState = new BrbState(meeting, enabled);
12
+
13
+ return brbState;
14
+ };
15
+
16
+ /** The purpose of this class is to manage the local and remote brb state
17
+ * and make sure that the server state always matches the last requested state by the client.
18
+ */
19
+ export class BrbState {
20
+ state: {
21
+ client: {
22
+ enabled: boolean;
23
+ };
24
+ server: {
25
+ enabled: boolean;
26
+ };
27
+ syncToServerInProgress: boolean;
28
+ };
29
+
30
+ meeting: Meeting;
31
+
32
+ /**
33
+ * Constructor
34
+ *
35
+ * @param {Meeting} meeting - the meeting object
36
+ * @param {boolean} enabled - whether the client audio/video is enabled at all
37
+ */
38
+ constructor(meeting: Meeting, enabled: boolean) {
39
+ this.meeting = meeting;
40
+ this.state = {
41
+ client: {
42
+ enabled,
43
+ },
44
+ server: {
45
+ enabled: false,
46
+ },
47
+ syncToServerInProgress: false,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Enables/disables brb
53
+ *
54
+ * @param {boolean} enabled
55
+ * @param {SendSlotManager} sendSlotManager
56
+ * @returns {Promise}
57
+ */
58
+ public enable(enabled: boolean, sendSlotManager: SendSlotManager) {
59
+ this.state.client.enabled = enabled;
60
+
61
+ return this.applyClientStateToServer(sendSlotManager);
62
+ }
63
+
64
+ /**
65
+ * Updates the server local and remote brb values so that they match the current client desired state.
66
+ *
67
+ * @param {SendSlotManager} sendSlotManager
68
+ * @returns {Promise}
69
+ */
70
+ private applyClientStateToServer(sendSlotManager: SendSlotManager) {
71
+ if (this.state.syncToServerInProgress) {
72
+ LoggerProxy.logger.info(
73
+ `Meeting:brbState#applyClientStateToServer: request to server in progress, we need to wait for it to complete`
74
+ );
75
+
76
+ return Promise.resolve();
77
+ }
78
+
79
+ const remoteBrbRequiresSync = this.state.client.enabled !== this.state.server.enabled;
80
+
81
+ LoggerProxy.logger.info(
82
+ `Meeting:brbState#applyClientStateToServer: remoteBrbRequiresSync: ${remoteBrbRequiresSync}`
83
+ );
84
+
85
+ if (!remoteBrbRequiresSync) {
86
+ LoggerProxy.logger.info(
87
+ `Meeting:brbState#applyClientStateToServer: client state already matching server state, nothing to do`
88
+ );
89
+
90
+ return Promise.resolve();
91
+ }
92
+
93
+ this.state.syncToServerInProgress = true;
94
+
95
+ return this.sendLocalBrbStateToServer(sendSlotManager)
96
+ .then(() => {
97
+ this.state.syncToServerInProgress = false;
98
+ LoggerProxy.logger.info(
99
+ `Meeting:brbState#applyClientStateToServer: sync with server completed`
100
+ );
101
+
102
+ // need to check if a new sync is required, because this.state.client may have changed while we were doing the current sync
103
+ this.applyClientStateToServer(sendSlotManager);
104
+ })
105
+ .catch((e) => {
106
+ this.state.syncToServerInProgress = false;
107
+ LoggerProxy.logger.warn(`Meeting:brbState#applyClientStateToServer: error: ${e}`);
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Send the local brb state to the server
113
+ *
114
+ * @param {SendSlotManager} sendSlotManager
115
+ * @returns {Promise}
116
+ */
117
+ private async sendLocalBrbStateToServer(sendSlotManager: SendSlotManager) {
118
+ const {enabled} = this.state.client;
119
+
120
+ if (!this.meeting.isMultistream) {
121
+ const errorMessage = 'Meeting:brbState#sendLocalBrbStateToServer: Not a multistream meeting';
122
+ const error = new Error(errorMessage);
123
+
124
+ LoggerProxy.logger.error(error);
125
+
126
+ return Promise.reject(error);
127
+ }
128
+
129
+ if (!this.meeting.mediaProperties.webrtcMediaConnection) {
130
+ const errorMessage =
131
+ 'Meeting:brbState#sendLocalBrbStateToServer: WebRTC media connection is not defined';
132
+ const error = new Error(errorMessage);
133
+
134
+ LoggerProxy.logger.error(error);
135
+
136
+ return Promise.reject(error);
137
+ }
138
+
139
+ // this logic should be applied only to multistream meetings
140
+ return this.meeting.meetingRequest
141
+ .setBrb({
142
+ enabled,
143
+ locusUrl: this.meeting.locusUrl,
144
+ deviceUrl: this.meeting.deviceUrl,
145
+ selfId: this.meeting.selfId,
146
+ })
147
+ .then(() => {
148
+ sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
149
+ })
150
+ .catch((error) => {
151
+ LoggerProxy.logger.error('Meeting:brbState#sendLocalBrbStateToServer: Error ', error);
152
+
153
+ return Promise.reject(error);
154
+ });
155
+ }
156
+
157
+ /**
158
+ * This method should be called whenever the server brb state is changed
159
+ *
160
+ * @param {Boolean} [enabled] true if user has brb enabled, false otherwise
161
+ * @returns {undefined}
162
+ */
163
+ public handleServerBrbUpdate(enabled?: boolean) {
164
+ LoggerProxy.logger.info(
165
+ `Meeting:brbState#handleServerBrbUpdate: updating server brb to (${enabled})`
166
+ );
167
+ this.state.server.enabled = !!enabled;
168
+ }
169
+ }
@@ -163,6 +163,7 @@ import {LocusMediaRequest} from './locusMediaRequest';
163
163
  import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
164
164
  import JoinWebinarError from '../common/errors/join-webinar-error';
165
165
  import Member from '../member';
166
+ import {BrbState, createBrbState} from './brbState';
166
167
  import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
167
168
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
168
169
 
@@ -649,6 +650,7 @@ export default class Meeting extends StatelessWebexPlugin {
649
650
  turnServerUsed: boolean;
650
651
  areVoiceaEventsSetup = false;
651
652
  isMoveToInProgress = false;
653
+ brbState: BrbState;
652
654
 
653
655
  voiceaListenerCallbacks: object = {
654
656
  [VOICEAEVENTS.VOICEA_ANNOUNCEMENT]: (payload: Transcription['languageOptions']) => {
@@ -3407,6 +3409,7 @@ export default class Meeting extends StatelessWebexPlugin {
3407
3409
  });
3408
3410
 
3409
3411
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED, (payload) => {
3412
+ this.brbState?.handleServerBrbUpdate(payload?.brb?.enabled);
3410
3413
  Trigger.trigger(
3411
3414
  this,
3412
3415
  {
@@ -3650,22 +3653,7 @@ export default class Meeting extends StatelessWebexPlugin {
3650
3653
  return Promise.reject(error);
3651
3654
  }
3652
3655
 
3653
- // this logic should be applied only to multistream meetings
3654
- return this.meetingRequest
3655
- .setBrb({
3656
- enabled,
3657
- locusUrl: this.locusUrl,
3658
- deviceUrl: this.deviceUrl,
3659
- selfId: this.selfId,
3660
- })
3661
- .then(() => {
3662
- this.sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
3663
- })
3664
- .catch((error) => {
3665
- LoggerProxy.logger.error('Meeting:index#beRightBack --> Error ', error);
3666
-
3667
- return Promise.reject(error);
3668
- });
3656
+ return this.brbState.enable(enabled, this.sendSlotManager);
3669
3657
  }
3670
3658
 
3671
3659
  /**
@@ -6099,9 +6087,12 @@ export default class Meeting extends StatelessWebexPlugin {
6099
6087
  * @returns {undefined}
6100
6088
  */
6101
6089
  public roapMessageReceived = (roapMessage: RoapMessage) => {
6102
- const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
6090
+ const mediaServer =
6091
+ roapMessage.messageType === 'ANSWER'
6092
+ ? MeetingsUtil.getMediaServer(roapMessage.sdp)
6093
+ : undefined;
6103
6094
 
6104
- if (this.isMultistream && mediaServer !== 'homer') {
6095
+ if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
6105
6096
  throw new MultistreamNotSupportedError(
6106
6097
  `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
6107
6098
  );
@@ -7432,6 +7423,7 @@ export default class Meeting extends StatelessWebexPlugin {
7432
7423
 
7433
7424
  this.audio = createMuteState(AUDIO, this, audioEnabled);
7434
7425
  this.video = createMuteState(VIDEO, this, videoEnabled);
7426
+ this.brbState = createBrbState(this, false);
7435
7427
 
7436
7428
  try {
7437
7429
  await this.setUpLocalStreamReferences(localStreams);
@@ -0,0 +1,114 @@
1
+ import sinon from 'sinon';
2
+ import {assert} from '@webex/test-helper-chai';
3
+
4
+ import testUtils from '../../../utils/testUtils';
5
+ import {BrbState, createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
6
+
7
+ describe('plugin-meetings', () => {
8
+ let meeting: any;
9
+ let brbState: BrbState;
10
+
11
+ beforeEach(async () => {
12
+ meeting = {
13
+ isMultistream: true,
14
+ locusUrl: 'locus url',
15
+ deviceUrl: 'device url',
16
+ selfId: 'self id',
17
+ mediaProperties: {
18
+ webrtcMediaConnection: true,
19
+ },
20
+ sendSlotManager: {
21
+ setSourceStateOverride: sinon.stub(),
22
+ },
23
+ meetingRequest: {
24
+ setBrb: sinon.stub().resolves(),
25
+ },
26
+ };
27
+
28
+ brbState = new BrbState(meeting, false);
29
+ await testUtils.flushPromises();
30
+ });
31
+
32
+ describe('brbState library', () => {
33
+ it('takes into account current status when instantiated', async () => {
34
+ // create a new BrbState instance
35
+ brbState = createBrbState(meeting, true);
36
+ await testUtils.flushPromises();
37
+
38
+ assert.isTrue(brbState.state.client.enabled);
39
+
40
+ // now check the opposite case
41
+ brbState = createBrbState(meeting, false);
42
+ await testUtils.flushPromises();
43
+
44
+ assert.isFalse(brbState.state.client.enabled);
45
+ });
46
+
47
+ it('can be enabled', async () => {
48
+ brbState.enable(true, meeting.sendSlotManager);
49
+ brbState.handleServerBrbUpdate(true);
50
+ await testUtils.flushPromises();
51
+
52
+ assert.isTrue(brbState.state.client.enabled);
53
+ assert.isTrue(brbState.state.server.enabled);
54
+ });
55
+
56
+ it('can be disabled', async () => {
57
+ brbState.enable(false, meeting.sendSlotManager);
58
+ brbState.handleServerBrbUpdate(false);
59
+ await testUtils.flushPromises();
60
+
61
+ assert.isFalse(brbState.state.client.enabled);
62
+ assert.isFalse(brbState.state.server.enabled);
63
+ });
64
+
65
+ it('does not send local brb state to server if it is not a multistream meeting', async () => {
66
+ meeting.isMultistream = false;
67
+ brbState.enable(true, meeting.sendSlotManager);
68
+ brbState.handleServerBrbUpdate(true);
69
+ await testUtils.flushPromises();
70
+
71
+ assert.isTrue(meeting.meetingRequest.setBrb.notCalled);
72
+ });
73
+
74
+ it('does not send local brb state to server if webrtc media connection is not defined', async () => {
75
+ meeting.mediaProperties.webrtcMediaConnection = undefined;
76
+ brbState.enable(true, meeting.sendSlotManager);
77
+ brbState.handleServerBrbUpdate(true);
78
+ await testUtils.flushPromises();
79
+
80
+ assert.isTrue(meeting.meetingRequest.setBrb.notCalled);
81
+ });
82
+
83
+ it('does not send request twice when in progress', async () => {
84
+ brbState.state.syncToServerInProgress = true;
85
+ brbState.enable(true, meeting.sendSlotManager);
86
+ await testUtils.flushPromises();
87
+
88
+ assert.isTrue(meeting.meetingRequest.setBrb.notCalled);
89
+ });
90
+
91
+ it('syncs with server when client state does not match server state', async () => {
92
+ brbState.enable(true, meeting.sendSlotManager);
93
+ brbState.handleServerBrbUpdate(true);
94
+ await testUtils.flushPromises();
95
+
96
+ assert.isTrue(meeting.meetingRequest.setBrb.calledOnce);
97
+ });
98
+
99
+ it('sets source state override when client state does not match server state', async () => {
100
+ brbState.enable(true, meeting.sendSlotManager);
101
+ brbState.handleServerBrbUpdate(true);
102
+ await testUtils.flushPromises();
103
+
104
+ assert.isTrue(meeting.sendSlotManager.setSourceStateOverride.calledOnce);
105
+ });
106
+
107
+ it('handles server update', async () => {
108
+ brbState.handleServerBrbUpdate(true);
109
+ await testUtils.flushPromises();
110
+
111
+ assert.isTrue(brbState.state.server.enabled);
112
+ });
113
+ });
114
+ });
@@ -114,6 +114,7 @@ import {ERROR_DESCRIPTIONS} from '@webex/internal-plugin-metrics/src/call-diagno
114
114
  import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
115
115
 
116
116
  import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
117
+ import { createBrbState } from '@webex/plugin-meetings/src/meeting/brbState';
117
118
  import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
118
119
 
119
120
  describe('plugin-meetings', () => {
@@ -3812,7 +3813,6 @@ describe('plugin-meetings', () => {
3812
3813
  };
3813
3814
 
3814
3815
  beforeEach(() => {
3815
- meeting.meetingRequest.setBrb = sinon.stub().resolves({body: 'test'});
3816
3816
  meeting.mediaProperties.webrtcMediaConnection = {createSendSlot: sinon.stub()};
3817
3817
  meeting.sendSlotManager.createSlot(
3818
3818
  fakeMultistreamRoapMediaConnection,
@@ -3822,6 +3822,8 @@ describe('plugin-meetings', () => {
3822
3822
  meeting.locusUrl = 'locus url';
3823
3823
  meeting.deviceUrl = 'device url';
3824
3824
  meeting.selfId = 'self id';
3825
+ meeting.brbState = createBrbState(meeting, false);
3826
+ meeting.brbState.enable = sinon.stub().resolves();
3825
3827
  });
3826
3828
 
3827
3829
  afterEach(() => {
@@ -3843,7 +3845,7 @@ describe('plugin-meetings', () => {
3843
3845
 
3844
3846
  await brbResult;
3845
3847
  assert.exists(brbResult.then);
3846
- assert.calledOnce(meeting.meetingRequest.setBrb);
3848
+ assert.calledOnce(meeting.brbState.enable);
3847
3849
  })
3848
3850
 
3849
3851
  it('should disable #beRightBack and return a promise', async () => {
@@ -3851,12 +3853,12 @@ describe('plugin-meetings', () => {
3851
3853
 
3852
3854
  await brbResult;
3853
3855
  assert.exists(brbResult.then);
3854
- assert.calledOnce(meeting.meetingRequest.setBrb);
3856
+ assert.calledOnce(meeting.brbState.enable);
3855
3857
  })
3856
3858
 
3857
3859
  it('should throw an error and reject the promise if setBrb fails', async () => {
3858
3860
  const error = new Error('setBrb failed');
3859
- meeting.meetingRequest.setBrb.rejects(error);
3861
+ meeting.brbState.enable.rejects(error);
3860
3862
 
3861
3863
  try {
3862
3864
  await meeting.beRightBack(true);
@@ -3867,27 +3869,6 @@ describe('plugin-meetings', () => {
3867
3869
  }
3868
3870
  })
3869
3871
  });
3870
-
3871
- describe('when in a transcoded meeting', () => {
3872
-
3873
- beforeEach(() => {
3874
- meeting.isMultistream = false;
3875
- });
3876
-
3877
- it('should ignore enabling #beRightBack', async () => {
3878
- meeting.beRightBack(true);
3879
-
3880
- assert.isRejected((Promise.reject()));
3881
- assert.notCalled(meeting.meetingRequest.setBrb);
3882
- })
3883
-
3884
- it('should ignore disabling #beRightBack', async () => {
3885
- meeting.beRightBack(false);
3886
-
3887
- assert.isRejected((Promise.reject()));
3888
- assert.notCalled(meeting.meetingRequest.setBrb);
3889
- })
3890
- });
3891
3872
  });
3892
3873
 
3893
3874
  /* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
@@ -9230,6 +9211,7 @@ describe('plugin-meetings', () => {
9230
9211
 
9231
9212
  it('listens to the brb state changed event', () => {
9232
9213
  const assertBrb = (enabled) => {
9214
+ meeting.brbState = createBrbState(meeting, false);
9233
9215
  meeting.locusInfo.emit(
9234
9216
  { function: 'test', file: 'test' },
9235
9217
  LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
@@ -13260,7 +13242,7 @@ describe('plugin-meetings', () => {
13260
13242
 
13261
13243
  describe('#roapMessageReceived', () => {
13262
13244
  it('calls roapMessageReceived on the webrtc media connection', () => {
13263
- const fakeMessage = {messageType: 'fake', sdp: 'fake sdp'};
13245
+ const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
13264
13246
 
13265
13247
  const getMediaServer = sinon.stub(MeetingsUtil, 'getMediaServer').returns('homer');
13266
13248
 
@@ -13298,5 +13280,26 @@ describe('plugin-meetings', () => {
13298
13280
 
13299
13281
  assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived);
13300
13282
  });
13283
+
13284
+ it('does not call getMediaServer for a roap message other than ANSWER', async () => {
13285
+ const fakeMessage = {messageType: 'ERROR', sdp: 'fake sdp'};
13286
+
13287
+ meeting.isMultistream = true;
13288
+ meeting.mediaProperties.webrtcMediaConnection = {
13289
+ roapMessageReceived: sinon.stub(),
13290
+ };
13291
+ meeting.mediaProperties.webrtcMediaConnection.mediaServer = 'linus';
13292
+
13293
+ const getMediaServerStub = sinon.stub(MeetingsUtil, 'getMediaServer').returns('something');
13294
+
13295
+ meeting.roapMessageReceived(fakeMessage);
13296
+
13297
+ assert.calledOnceWithExactly(
13298
+ meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived,
13299
+ fakeMessage
13300
+ );
13301
+ assert.notCalled(getMediaServerStub);
13302
+ assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'linus'); // check that it hasn't been overwritten
13303
+ });
13301
13304
  });
13302
13305
  });