@webex/plugin-meetings 3.0.0-beta.17 → 3.0.0-beta.19

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.
Files changed (152) hide show
  1. package/dist/breakouts/breakout.js +116 -0
  2. package/dist/breakouts/breakout.js.map +1 -0
  3. package/dist/breakouts/collection.js +23 -0
  4. package/dist/breakouts/collection.js.map +1 -0
  5. package/dist/breakouts/index.js +226 -0
  6. package/dist/breakouts/index.js.map +1 -0
  7. package/dist/config.js +4 -1
  8. package/dist/config.js.map +1 -1
  9. package/dist/constants.js +38 -5
  10. package/dist/constants.js.map +1 -1
  11. package/dist/locus-info/controlsUtils.js +2 -1
  12. package/dist/locus-info/controlsUtils.js.map +1 -1
  13. package/dist/locus-info/index.js +48 -0
  14. package/dist/locus-info/index.js.map +1 -1
  15. package/dist/locus-info/parser.js +1 -0
  16. package/dist/locus-info/parser.js.map +1 -1
  17. package/dist/locus-info/selfUtils.js +19 -11
  18. package/dist/locus-info/selfUtils.js.map +1 -1
  19. package/dist/media/index.js +3 -3
  20. package/dist/media/index.js.map +1 -1
  21. package/dist/media/properties.js +4 -4
  22. package/dist/media/properties.js.map +1 -1
  23. package/dist/meeting/index.js +719 -490
  24. package/dist/meeting/index.js.map +1 -1
  25. package/dist/meeting/request.js +25 -44
  26. package/dist/meeting/request.js.map +1 -1
  27. package/dist/meeting/request.type.js.map +1 -1
  28. package/dist/meeting/util.js +4 -57
  29. package/dist/meeting/util.js.map +1 -1
  30. package/dist/meeting-info/meeting-info-v2.js +2 -0
  31. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  32. package/dist/meetings/index.js +28 -18
  33. package/dist/meetings/index.js.map +1 -1
  34. package/dist/meetings/request.js +14 -12
  35. package/dist/meetings/request.js.map +1 -1
  36. package/dist/member/index.js +9 -0
  37. package/dist/member/index.js.map +1 -1
  38. package/dist/member/util.js +14 -1
  39. package/dist/member/util.js.map +1 -1
  40. package/dist/members/index.js +8 -6
  41. package/dist/members/index.js.map +1 -1
  42. package/dist/members/request.js +3 -1
  43. package/dist/members/request.js.map +1 -1
  44. package/dist/multistream/mediaRequestManager.js +46 -6
  45. package/dist/multistream/mediaRequestManager.js.map +1 -1
  46. package/dist/multistream/multistreamMedia.js +4 -0
  47. package/dist/multistream/multistreamMedia.js.map +1 -1
  48. package/dist/multistream/receiveSlot.js +3 -3
  49. package/dist/multistream/receiveSlot.js.map +1 -1
  50. package/dist/multistream/receiveSlotManager.js +8 -6
  51. package/dist/multistream/receiveSlotManager.js.map +1 -1
  52. package/dist/multistream/remoteMedia.js.map +1 -1
  53. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  54. package/dist/multistream/remoteMediaManager.js +168 -63
  55. package/dist/multistream/remoteMediaManager.js.map +1 -1
  56. package/dist/reachability/index.js +63 -51
  57. package/dist/reachability/index.js.map +1 -1
  58. package/dist/reactions/constants.js +13 -0
  59. package/dist/reactions/constants.js.map +1 -0
  60. package/dist/reactions/reactions.type.js.map +1 -1
  61. package/dist/reconnection-manager/index.js +25 -12
  62. package/dist/reconnection-manager/index.js.map +1 -1
  63. package/dist/recording-controller/enums.js +17 -0
  64. package/dist/recording-controller/enums.js.map +1 -0
  65. package/dist/recording-controller/index.js +343 -0
  66. package/dist/recording-controller/index.js.map +1 -0
  67. package/dist/recording-controller/util.js +63 -0
  68. package/dist/recording-controller/util.js.map +1 -0
  69. package/dist/roap/request.js +88 -68
  70. package/dist/roap/request.js.map +1 -1
  71. package/dist/roap/turnDiscovery.js +72 -47
  72. package/dist/roap/turnDiscovery.js.map +1 -1
  73. package/dist/statsAnalyzer/index.js +3 -3
  74. package/dist/statsAnalyzer/index.js.map +1 -1
  75. package/dist/statsAnalyzer/mqaUtil.js +18 -6
  76. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  77. package/package.json +24 -19
  78. package/src/breakouts/README.md +190 -0
  79. package/src/breakouts/breakout.ts +110 -0
  80. package/src/breakouts/collection.ts +19 -0
  81. package/src/breakouts/index.ts +225 -0
  82. package/src/config.ts +4 -1
  83. package/src/constants.ts +35 -1
  84. package/src/locus-info/controlsUtils.ts +2 -0
  85. package/src/locus-info/index.ts +59 -1
  86. package/src/locus-info/parser.ts +1 -0
  87. package/src/locus-info/selfUtils.ts +8 -0
  88. package/src/media/index.ts +1 -2
  89. package/src/media/properties.ts +6 -9
  90. package/src/meeting/index.ts +398 -129
  91. package/src/meeting/request.ts +9 -31
  92. package/src/meeting/request.type.ts +2 -0
  93. package/src/meeting/util.ts +3 -60
  94. package/src/meeting-info/meeting-info-v2.ts +2 -0
  95. package/src/meetings/index.ts +10 -5
  96. package/src/meetings/request.ts +1 -1
  97. package/src/member/index.ts +9 -0
  98. package/src/member/util.ts +14 -1
  99. package/src/members/index.ts +1 -0
  100. package/src/members/request.ts +1 -0
  101. package/src/multistream/mediaRequestManager.ts +79 -15
  102. package/src/multistream/multistreamMedia.ts +4 -0
  103. package/src/multistream/receiveSlot.ts +17 -12
  104. package/src/multistream/receiveSlotManager.ts +22 -21
  105. package/src/multistream/remoteMedia.ts +1 -1
  106. package/src/multistream/remoteMediaGroup.ts +2 -2
  107. package/src/multistream/remoteMediaManager.ts +150 -37
  108. package/src/reachability/index.ts +16 -13
  109. package/src/reactions/constants.ts +4 -0
  110. package/src/reactions/reactions.type.ts +25 -0
  111. package/src/reconnection-manager/index.ts +18 -9
  112. package/src/recording-controller/enums.ts +8 -0
  113. package/src/recording-controller/index.ts +315 -0
  114. package/src/recording-controller/util.ts +58 -0
  115. package/src/roap/request.ts +78 -73
  116. package/src/roap/turnDiscovery.ts +8 -6
  117. package/src/statsAnalyzer/index.ts +4 -4
  118. package/src/statsAnalyzer/mqaUtil.ts +6 -0
  119. package/test/unit/spec/breakouts/breakout.ts +119 -0
  120. package/test/unit/spec/breakouts/collection.ts +15 -0
  121. package/test/unit/spec/breakouts/index.ts +293 -0
  122. package/test/unit/spec/locus-info/controlsUtils.js +20 -0
  123. package/test/unit/spec/locus-info/index.js +103 -0
  124. package/test/unit/spec/locus-info/selfConstant.js +25 -0
  125. package/test/unit/spec/locus-info/selfUtils.js +84 -0
  126. package/test/unit/spec/media/index.ts +1 -1
  127. package/test/unit/spec/media/properties.ts +9 -9
  128. package/test/unit/spec/meeting/effectsState.js +5 -1
  129. package/test/unit/spec/meeting/index.js +419 -50
  130. package/test/unit/spec/meeting/request.js +17 -0
  131. package/test/unit/spec/meeting/utils.js +20 -129
  132. package/test/unit/spec/meetings/index.js +1 -0
  133. package/test/unit/spec/member/util.js +26 -1
  134. package/test/unit/spec/multistream/mediaRequestManager.ts +312 -50
  135. package/test/unit/spec/multistream/receiveSlot.ts +6 -6
  136. package/test/unit/spec/multistream/receiveSlotManager.ts +13 -13
  137. package/test/unit/spec/multistream/remoteMedia.ts +2 -2
  138. package/test/unit/spec/multistream/remoteMediaGroup.ts +5 -5
  139. package/test/unit/spec/multistream/remoteMediaManager.ts +354 -65
  140. package/test/unit/spec/reachability/index.ts +58 -24
  141. package/test/unit/spec/reconnection-manager/index.js +42 -13
  142. package/test/unit/spec/recording-controller/index.js +231 -0
  143. package/test/unit/spec/recording-controller/util.js +102 -0
  144. package/test/unit/spec/roap/index.ts +2 -1
  145. package/test/unit/spec/roap/request.ts +114 -0
  146. package/test/unit/spec/roap/turnDiscovery.ts +45 -29
  147. package/test/unit/spec/stats-analyzer/index.js +2 -2
  148. package/test/utils/webex-test-users.js +1 -0
  149. package/tsconfig.json +6 -0
  150. package/dist/media/internal-media-core-wrapper.js +0 -18
  151. package/dist/media/internal-media-core-wrapper.js.map +0 -1
  152. package/src/media/internal-media-core-wrapper.ts +0 -9
@@ -22,7 +22,7 @@ import {
22
22
  LOCUSINFO,
23
23
  PC_BAIL_TIMEOUT,
24
24
  } from '@webex/plugin-meetings/src/constants';
25
- import {MediaConnection as MC} from '@webex/internal-media-core';
25
+ import {ConnectionState, Event, Errors, ErrorType, RemoteTrackType} from '@webex/internal-media-core';
26
26
  import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
27
27
  import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
28
28
  import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
@@ -36,6 +36,7 @@ import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
36
36
  import Media from '@webex/plugin-meetings/src/media/index';
37
37
  import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
38
38
  import MediaUtil from '@webex/plugin-meetings/src/media/util';
39
+ import RecordingUtil from '@webex/plugin-meetings/src/recording-controller/util';
39
40
  import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
40
41
  import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
41
42
  import TriggerProxy from '@webex/plugin-meetings/src/common/events/trigger-proxy';
@@ -44,7 +45,12 @@ import Metrics from '@webex/plugin-meetings/src/metrics';
44
45
  import {trigger, eventType} from '@webex/plugin-meetings/src/metrics/config';
45
46
  import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
46
47
  import {IceGatheringFailed} from '@webex/plugin-meetings/src/common/errors/webex-errors';
48
+ import {MediaRequestManager} from '@webex/plugin-meetings/src/multistream/mediaRequestManager';
47
49
 
50
+ import LLM from '@webex/internal-plugin-llm';
51
+ import Mercury from '@webex/internal-plugin-mercury';
52
+ import Breakouts from '@webex/plugin-meetings/src/breakouts';
53
+ import {REACTION_RELAY_TYPES} from '../../../../src/reactions/constants';
48
54
  import locus from '../fixture/locus';
49
55
  import {
50
56
  UserNotJoinedError,
@@ -155,6 +161,8 @@ describe('plugin-meetings', () => {
155
161
  meetings: Meetings,
156
162
  credentials: Credentials,
157
163
  support: Support,
164
+ llm: LLM,
165
+ mercury: Mercury,
158
166
  },
159
167
  config: {
160
168
  credentials: {
@@ -179,6 +187,7 @@ describe('plugin-meetings', () => {
179
187
  webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
180
188
  webex.internal.metrics.submitClientMetrics = sinon.stub().returns(Promise.resolve());
181
189
  webex.meetings.uploadLogs = sinon.stub().returns(Promise.resolve());
190
+ webex.internal.llm.on = sinon.stub();
182
191
 
183
192
  TriggerProxy.trigger = sinon.stub().returns(true);
184
193
  Metrics.postEvent = sinon.stub();
@@ -250,6 +259,13 @@ describe('plugin-meetings', () => {
250
259
  assert.equal(meeting.meetingInfoFailureReason, undefined);
251
260
  assert.equal(meeting.destination, testDestination);
252
261
  assert.equal(meeting.destinationType, _MEETING_ID_);
262
+ assert.instanceOf(meeting.breakouts, Breakouts);
263
+ });
264
+ it('creates MediaRequestManager instances', () => {
265
+ assert.instanceOf(meeting.mediaRequestManagers.audio, MediaRequestManager);
266
+ assert.instanceOf(meeting.mediaRequestManagers.video, MediaRequestManager);
267
+ assert.instanceOf(meeting.mediaRequestManagers.screenShareAudio, MediaRequestManager);
268
+ assert.instanceOf(meeting.mediaRequestManagers.screenShareVideo, MediaRequestManager);
253
269
  });
254
270
  });
255
271
  describe('#invite', () => {
@@ -779,7 +795,7 @@ describe('plugin-meetings', () => {
779
795
  });
780
796
  });
781
797
 
782
- it('should throw error', async () => {
798
+ it("should throw error if request doesn't work", async () => {
783
799
  meeting.request = sinon.stub().returns(Promise.reject());
784
800
 
785
801
  try {
@@ -799,6 +815,64 @@ describe('plugin-meetings', () => {
799
815
  assert.calledOnce(meeting.transcription.closeSocket);
800
816
  });
801
817
  });
818
+ describe('#isReactionsSupported', () => {
819
+ it('should return false if the feature is not supported for the meeting', () => {
820
+ meeting.locusInfo.controls = {reactions: {enabled: false}};
821
+
822
+ assert.equal(meeting.isReactionsSupported(), false);
823
+ });
824
+ it('should return true if the feature is not supported for the meeting', () => {
825
+ meeting.locusInfo.controls = {reactions: {enabled: true}};
826
+
827
+ assert.equal(meeting.isReactionsSupported(), true);
828
+ });
829
+ });
830
+ describe('#processRelayEvent', () => {
831
+ it('should process a Reaction event type', () => {
832
+ meeting.isReactionsSupported = sinon.stub().returns(true);
833
+ meeting.config.receiveReactions = true;
834
+ const fakeSendersName = 'Fake reactors name';
835
+ meeting.members.membersCollection.get = sinon.stub().returns({name: fakeSendersName});
836
+ const fakeReactionPayload = {
837
+ type: 'fake_type',
838
+ codepoints: 'fake_codepoints',
839
+ shortcodes: 'fake_shortcodes',
840
+ tone: {
841
+ type: 'fake_tone_type',
842
+ codepoints: 'fake_tone_codepoints',
843
+ shortcodes: 'fake_tone_shortcodes',
844
+ },
845
+ };
846
+ const fakeSenderPayload = {
847
+ participantId: 'fake_participant_id',
848
+ };
849
+ const fakeProcessedReaction = {
850
+ reaction: fakeReactionPayload,
851
+ sender: {
852
+ id: fakeSenderPayload.participantId,
853
+ name: fakeSendersName,
854
+ },
855
+ };
856
+ const fakeRelayEvent = {
857
+ data: {
858
+ relayType: REACTION_RELAY_TYPES.REACTION,
859
+ reaction: fakeReactionPayload,
860
+ sender: fakeSenderPayload,
861
+ }
862
+ };
863
+ meeting.processRelayEvent(fakeRelayEvent);
864
+ assert.calledWith(
865
+ TriggerProxy.trigger,
866
+ sinon.match.instanceOf(Meeting),
867
+ {
868
+ file: 'meeting/index',
869
+ function: 'join',
870
+ },
871
+ EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
872
+ fakeProcessedReaction
873
+ );
874
+ })
875
+ })
802
876
  describe('#join', () => {
803
877
  let sandbox = null;
804
878
  const joinMeetingResult = 'JOIN_MEETINGS_OPTION_RESULT';
@@ -844,15 +918,20 @@ describe('plugin-meetings', () => {
844
918
 
845
919
  it('should call updateLLMConnection upon joining if config value is set', async () => {
846
920
  meeting.config.enableAutomaticLLM = true;
921
+ meeting.webex.internal.llm.on = sinon.stub();
922
+ meeting.processRelayEvent = sinon.stub();
847
923
  await meeting.join();
848
924
 
849
925
  assert.calledOnce(meeting.updateLLMConnection);
926
+ assert.calledOnceWithExactly(meeting.webex.internal.llm.on, 'event:relay.event', meeting.processRelayEvent);
850
927
  });
851
928
 
852
929
  it('should not call updateLLMConnection upon joining if config value is not set', async () => {
930
+ meeting.webex.internal.llm.on = sinon.stub();
853
931
  await meeting.join();
854
932
 
855
933
  assert.notCalled(meeting.updateLLMConnection);
934
+ assert.notCalled(meeting.webex.internal.llm.on);
856
935
  });
857
936
 
858
937
  it('should invoke `receiveTranscription()` if receiveTranscription is set to true', async () => {
@@ -952,7 +1031,7 @@ describe('plugin-meetings', () => {
952
1031
  beforeEach(() => {
953
1032
  fakeMediaConnection = {
954
1033
  close: sinon.stub(),
955
- getConnectionState: sinon.stub().returns(MC.ConnectionState.Connected),
1034
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
956
1035
  initiateOffer: sinon.stub().resolves({}),
957
1036
  on: sinon.stub(),
958
1037
  };
@@ -1200,7 +1279,7 @@ describe('plugin-meetings', () => {
1200
1279
  meeting.meetingState = 'ACTIVE';
1201
1280
  fakeMediaConnection.getConnectionState = sinon
1202
1281
  .stub()
1203
- .returns(MC.ConnectionState.Connecting);
1282
+ .returns(ConnectionState.Connecting);
1204
1283
  const clock = sinon.useFakeTimers();
1205
1284
  const media = meeting.addMedia({
1206
1285
  mediaSettings: {},
@@ -2130,6 +2209,190 @@ describe('plugin-meetings', () => {
2130
2209
  );
2131
2210
  assert.isTrue(myPromiseResolved);
2132
2211
  });
2212
+
2213
+ it('should request floor only after roap transaction is completed', async () => {
2214
+ const eventListeners = {};
2215
+
2216
+ meeting.webex.meetings.reachability = {
2217
+ isAnyClusterReachable: sandbox.stub().resolves(true)
2218
+ };
2219
+
2220
+ const fakeMediaConnection = {
2221
+ close: sinon.stub(),
2222
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
2223
+ initiateOffer: sinon.stub().resolves({}),
2224
+
2225
+ // mock the on() method and store all the listeners
2226
+ on: sinon.stub().callsFake((event, listener) => {
2227
+ eventListeners[event] = listener;
2228
+ }),
2229
+
2230
+ updateSendReceiveOptions: sinon.stub().callsFake(() => {
2231
+ // trigger ROAP_STARTED before updateSendReceiveOptions() resolves
2232
+ if (eventListeners[Event.ROAP_STARTED]) {
2233
+ eventListeners[Event.ROAP_STARTED]();
2234
+ } else {
2235
+ throw new Error('ROAP_STARTED listener not registered')
2236
+ }
2237
+ return Promise.resolve();
2238
+ }),
2239
+ };
2240
+
2241
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2242
+ meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
2243
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
2244
+
2245
+ const requestScreenShareFloorStub = sandbox.stub(meeting, 'requestScreenShareFloor').resolves({});
2246
+
2247
+ let myPromiseResolved = false;
2248
+
2249
+ meeting.meetingState = 'ACTIVE';
2250
+ await meeting.addMedia({
2251
+ mediaSettings: {},
2252
+ });
2253
+
2254
+ meeting
2255
+ .updateMedia({
2256
+ localShare: mockLocalShare,
2257
+ mediaSettings: {
2258
+ sendShare: true,
2259
+ },
2260
+ })
2261
+ .then(() => {
2262
+ myPromiseResolved = true;
2263
+ });
2264
+
2265
+ await testUtils.flushPromises();
2266
+
2267
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2268
+ assert.isFalse(myPromiseResolved);
2269
+
2270
+ // verify that requestScreenShareFloorStub was not called yet
2271
+ assert.notCalled(requestScreenShareFloorStub);
2272
+
2273
+ eventListeners[Event.ROAP_DONE]();
2274
+ await testUtils.flushPromises();
2275
+
2276
+ // now it should have been called
2277
+ assert.calledOnce(requestScreenShareFloorStub);
2278
+ });
2279
+ });
2280
+
2281
+ describe('#updateShare', () => {
2282
+ const mockLocalShare = {id: 'mock local share stream'};
2283
+ let eventListeners;
2284
+ let fakeMediaConnection;
2285
+ let requestScreenShareFloorStub;
2286
+
2287
+ const FAKE_TRACKS = {
2288
+ screenshareVideo: {
2289
+ id: 'fake share track',
2290
+ getSettings: sinon.stub().returns({}),
2291
+ },
2292
+ };
2293
+
2294
+ beforeEach(async () => {
2295
+ eventListeners = {};
2296
+
2297
+ sinon.stub(MeetingUtil, 'getTrack').callsFake((stream) => {
2298
+ if (stream === mockLocalShare) {
2299
+ return {audioTrack: null, videoTrack: FAKE_TRACKS.screenshareVideo};
2300
+ }
2301
+
2302
+ return {audioTrack: null, videoTrack: null};
2303
+ });
2304
+
2305
+ meeting.webex.meetings.reachability = {
2306
+ isAnyClusterReachable: sinon.stub().resolves(true)
2307
+ };
2308
+
2309
+ fakeMediaConnection = {
2310
+ close: sinon.stub(),
2311
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
2312
+ initiateOffer: sinon.stub().resolves({}),
2313
+
2314
+ // mock the on() method and store all the listeners
2315
+ on: sinon.stub().callsFake((event, listener) => {
2316
+ eventListeners[event] = listener;
2317
+ }),
2318
+
2319
+ updateSendReceiveOptions: sinon.stub().callsFake(() => {
2320
+ // trigger ROAP_STARTED before updateSendReceiveOptions() resolves
2321
+ if (eventListeners[Event.ROAP_STARTED]) {
2322
+ eventListeners[Event.ROAP_STARTED]();
2323
+ } else {
2324
+ throw new Error('ROAP_STARTED listener not registered')
2325
+ }
2326
+ return Promise.resolve();
2327
+ }),
2328
+ };
2329
+
2330
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
2331
+ meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
2332
+ Media.createMediaConnection = sinon.stub().returns(fakeMediaConnection);
2333
+
2334
+ requestScreenShareFloorStub = sinon.stub(meeting, 'requestScreenShareFloor').resolves({});
2335
+
2336
+ meeting.meetingState = 'ACTIVE';
2337
+ await meeting.addMedia({
2338
+ mediaSettings: {},
2339
+ });
2340
+ });
2341
+
2342
+ afterEach(() => {
2343
+ sinon.restore();
2344
+ });
2345
+
2346
+ it('when starting share, it should request floor only after roap transaction is completed', async () => {
2347
+ let myPromiseResolved = false;
2348
+
2349
+ meeting
2350
+ .updateShare({
2351
+ sendShare: true,
2352
+ receiveShare: true,
2353
+ stream: mockLocalShare,
2354
+ })
2355
+ .then(() => {
2356
+ myPromiseResolved = true;
2357
+ });
2358
+
2359
+ await testUtils.flushPromises();
2360
+
2361
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2362
+ assert.isFalse(myPromiseResolved);
2363
+
2364
+ // verify that requestScreenShareFloorStub was not called yet
2365
+ assert.notCalled(requestScreenShareFloorStub);
2366
+
2367
+ eventListeners[Event.ROAP_DONE]();
2368
+ await testUtils.flushPromises();
2369
+
2370
+ // now it should have been called
2371
+ assert.calledOnce(requestScreenShareFloorStub);
2372
+ });
2373
+
2374
+ it('when changing screen share stream and no roap transaction happening, it requests floor immediately', async () => {
2375
+ let myPromiseResolved = false;
2376
+
2377
+ // simulate a case when no roap transaction is triggered by updateSendReceiveOptions
2378
+ meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions = sinon.stub().resolves({});
2379
+
2380
+ meeting
2381
+ .updateShare({
2382
+ sendShare: true,
2383
+ receiveShare: true,
2384
+ stream: mockLocalShare,
2385
+ })
2386
+ .then(() => {
2387
+ myPromiseResolved = true;
2388
+ });
2389
+
2390
+ await testUtils.flushPromises();
2391
+
2392
+ assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions);
2393
+ assert.calledOnce(requestScreenShareFloorStub);
2394
+ assert.isTrue(myPromiseResolved);
2395
+ });
2133
2396
  });
2134
2397
 
2135
2398
  describe('#changeVideoLayout', () => {
@@ -3531,19 +3794,19 @@ describe('plugin-meetings', () => {
3531
3794
 
3532
3795
  it('should register for all the correct RoapMediaConnection events', () => {
3533
3796
  meeting.setupMediaConnectionListeners();
3534
- assert.isFunction(eventListeners[MC.Event.ROAP_STARTED]);
3535
- assert.isFunction(eventListeners[MC.Event.ROAP_DONE]);
3536
- assert.isFunction(eventListeners[MC.Event.ROAP_FAILURE]);
3537
- assert.isFunction(eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]);
3538
- assert.isFunction(eventListeners[MC.Event.REMOTE_TRACK_ADDED]);
3539
- assert.isFunction(eventListeners[MC.Event.CONNECTION_STATE_CHANGED]);
3797
+ assert.isFunction(eventListeners[Event.ROAP_STARTED]);
3798
+ assert.isFunction(eventListeners[Event.ROAP_DONE]);
3799
+ assert.isFunction(eventListeners[Event.ROAP_FAILURE]);
3800
+ assert.isFunction(eventListeners[Event.ROAP_MESSAGE_TO_SEND]);
3801
+ assert.isFunction(eventListeners[Event.REMOTE_TRACK_ADDED]);
3802
+ assert.isFunction(eventListeners[Event.CONNECTION_STATE_CHANGED]);
3540
3803
  });
3541
3804
 
3542
3805
  it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
3543
3806
  meeting.setupMediaConnectionListeners();
3544
- eventListeners[MC.Event.REMOTE_TRACK_ADDED]({
3807
+ eventListeners[Event.REMOTE_TRACK_ADDED]({
3545
3808
  track: 'track',
3546
- type: MC.RemoteTrackType.AUDIO,
3809
+ type: RemoteTrackType.AUDIO,
3547
3810
  });
3548
3811
  assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:ready');
3549
3812
  assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {
@@ -3551,9 +3814,9 @@ describe('plugin-meetings', () => {
3551
3814
  stream: true,
3552
3815
  });
3553
3816
 
3554
- eventListeners[MC.Event.REMOTE_TRACK_ADDED]({
3817
+ eventListeners[Event.REMOTE_TRACK_ADDED]({
3555
3818
  track: 'track',
3556
- type: MC.RemoteTrackType.VIDEO,
3819
+ type: RemoteTrackType.VIDEO,
3557
3820
  });
3558
3821
  assert.equal(TriggerProxy.trigger.getCall(2).args[2], 'media:ready');
3559
3822
  assert.deepEqual(TriggerProxy.trigger.getCall(2).args[3], {
@@ -3561,9 +3824,9 @@ describe('plugin-meetings', () => {
3561
3824
  stream: true,
3562
3825
  });
3563
3826
 
3564
- eventListeners[MC.Event.REMOTE_TRACK_ADDED]({
3827
+ eventListeners[Event.REMOTE_TRACK_ADDED]({
3565
3828
  track: 'track',
3566
- type: MC.RemoteTrackType.SCREENSHARE_VIDEO,
3829
+ type: RemoteTrackType.SCREENSHARE_VIDEO,
3567
3830
  });
3568
3831
  assert.equal(TriggerProxy.trigger.getCall(3).args[2], 'media:ready');
3569
3832
  assert.deepEqual(TriggerProxy.trigger.getCall(3).args[3], {
@@ -3613,51 +3876,51 @@ describe('plugin-meetings', () => {
3613
3876
  };
3614
3877
 
3615
3878
  it('should send metrics for SdpOfferCreationError error', () => {
3616
- const fakeError = new MC.Errors.SdpOfferCreationError(fakeErrorMessage, {
3879
+ const fakeError = new Errors.SdpOfferCreationError(fakeErrorMessage, {
3617
3880
  name: fakeErrorName,
3618
3881
  cause: {name: fakeRootCauseName},
3619
3882
  });
3620
3883
 
3621
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3884
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3622
3885
 
3623
3886
  checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3624
3887
  checkBehavioralMetricSent(
3625
3888
  BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
3626
- MC.Errors.ErrorCode.SdpOfferCreationError,
3889
+ Errors.ErrorCode.SdpOfferCreationError,
3627
3890
  fakeErrorMessage,
3628
3891
  fakeRootCauseName
3629
3892
  );
3630
3893
  });
3631
3894
 
3632
3895
  it('should send metrics for SdpOfferHandlingError error', () => {
3633
- const fakeError = new MC.Errors.SdpOfferHandlingError(fakeErrorMessage, {
3896
+ const fakeError = new Errors.SdpOfferHandlingError(fakeErrorMessage, {
3634
3897
  name: fakeErrorName,
3635
3898
  cause: {name: fakeRootCauseName},
3636
3899
  });
3637
3900
 
3638
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3901
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3639
3902
 
3640
3903
  checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
3641
3904
  checkBehavioralMetricSent(
3642
3905
  BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
3643
- MC.Errors.ErrorCode.SdpOfferHandlingError,
3906
+ Errors.ErrorCode.SdpOfferHandlingError,
3644
3907
  fakeErrorMessage,
3645
3908
  fakeRootCauseName
3646
3909
  );
3647
3910
  });
3648
3911
 
3649
3912
  it('should send metrics for SdpAnswerHandlingError error', () => {
3650
- const fakeError = new MC.Errors.SdpAnswerHandlingError(fakeErrorMessage, {
3913
+ const fakeError = new Errors.SdpAnswerHandlingError(fakeErrorMessage, {
3651
3914
  name: fakeErrorName,
3652
3915
  cause: {name: fakeRootCauseName},
3653
3916
  });
3654
3917
 
3655
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3918
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3656
3919
 
3657
3920
  checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
3658
3921
  checkBehavioralMetricSent(
3659
3922
  BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
3660
- MC.Errors.ErrorCode.SdpAnswerHandlingError,
3923
+ Errors.ErrorCode.SdpAnswerHandlingError,
3661
3924
  fakeErrorMessage,
3662
3925
  fakeRootCauseName
3663
3926
  );
@@ -3665,15 +3928,15 @@ describe('plugin-meetings', () => {
3665
3928
 
3666
3929
  it('should send metrics for SdpError error', () => {
3667
3930
  // SdpError is usually without a cause
3668
- const fakeError = new MC.Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
3931
+ const fakeError = new Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
3669
3932
 
3670
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3933
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3671
3934
 
3672
3935
  checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3673
3936
  // expectedMetadataType is the error name in this case
3674
3937
  checkBehavioralMetricSent(
3675
3938
  BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
3676
- MC.Errors.ErrorCode.SdpError,
3939
+ Errors.ErrorCode.SdpError,
3677
3940
  fakeErrorMessage,
3678
3941
  fakeErrorName
3679
3942
  );
@@ -3681,24 +3944,24 @@ describe('plugin-meetings', () => {
3681
3944
 
3682
3945
  it('should send metrics for IceGatheringError error', () => {
3683
3946
  // IceGatheringError is usually without a cause
3684
- const fakeError = new MC.Errors.IceGatheringError(fakeErrorMessage, {
3947
+ const fakeError = new Errors.IceGatheringError(fakeErrorMessage, {
3685
3948
  name: fakeErrorName,
3686
3949
  });
3687
3950
 
3688
- eventListeners[MC.Event.ROAP_FAILURE](fakeError);
3951
+ eventListeners[Event.ROAP_FAILURE](fakeError);
3689
3952
 
3690
3953
  checkMetricSent(eventType.LOCAL_SDP_GENERATED);
3691
3954
  // expectedMetadataType is the error name in this case
3692
3955
  checkBehavioralMetricSent(
3693
3956
  BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
3694
- MC.Errors.ErrorCode.IceGatheringError,
3957
+ Errors.ErrorCode.IceGatheringError,
3695
3958
  fakeErrorMessage,
3696
3959
  fakeErrorName
3697
3960
  );
3698
3961
  });
3699
3962
  });
3700
3963
 
3701
- describe('handles MC.Event.ROAP_MESSAGE_TO_SEND correctly', () => {
3964
+ describe('handles Event.ROAP_MESSAGE_TO_SEND correctly', () => {
3702
3965
  let sendRoapOKStub;
3703
3966
  let sendRoapMediaRequestStub;
3704
3967
  let sendRoapAnswerStub;
@@ -3716,7 +3979,7 @@ describe('plugin-meetings', () => {
3716
3979
  });
3717
3980
 
3718
3981
  it('handles OK message correctly', () => {
3719
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
3982
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3720
3983
  roapMessage: {messageType: 'OK', seq: 1},
3721
3984
  });
3722
3985
 
@@ -3735,7 +3998,7 @@ describe('plugin-meetings', () => {
3735
3998
  });
3736
3999
 
3737
4000
  it('handles OFFER message correctly', () => {
3738
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4001
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3739
4002
  roapMessage: {
3740
4003
  messageType: 'OFFER',
3741
4004
  seq: 1,
@@ -3761,7 +4024,7 @@ describe('plugin-meetings', () => {
3761
4024
  });
3762
4025
 
3763
4026
  it('handles ANSWER message correctly', () => {
3764
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4027
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3765
4028
  roapMessage: {
3766
4029
  messageType: 'ANSWER',
3767
4030
  seq: 10,
@@ -3788,7 +4051,7 @@ describe('plugin-meetings', () => {
3788
4051
  it('sends metrics if fails to send roap ANSWER message', async () => {
3789
4052
  sendRoapAnswerStub.rejects(new Error('sending answer failed'));
3790
4053
 
3791
- await eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4054
+ await eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3792
4055
  roapMessage: {
3793
4056
  messageType: 'ANSWER',
3794
4057
  seq: 10,
@@ -3810,9 +4073,9 @@ describe('plugin-meetings', () => {
3810
4073
  );
3811
4074
  });
3812
4075
 
3813
- [MC.ErrorType.CONFLICT, MC.ErrorType.DOUBLECONFLICT].forEach((errorType) =>
4076
+ [ErrorType.CONFLICT, ErrorType.DOUBLECONFLICT].forEach((errorType) =>
3814
4077
  it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
3815
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4078
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3816
4079
  roapMessage: {
3817
4080
  messageType: 'ERROR',
3818
4081
  seq: 10,
@@ -3843,11 +4106,11 @@ describe('plugin-meetings', () => {
3843
4106
  );
3844
4107
 
3845
4108
  it('handles ERROR message indicating other errors correctly', () => {
3846
- eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
4109
+ eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
3847
4110
  roapMessage: {
3848
4111
  messageType: 'ERROR',
3849
4112
  seq: 10,
3850
- errorType: MC.ErrorType.FAILED,
4113
+ errorType: ErrorType.FAILED,
3851
4114
  tieBreaker: 12345,
3852
4115
  },
3853
4116
  });
@@ -3857,7 +4120,7 @@ describe('plugin-meetings', () => {
3857
4120
  assert.calledOnce(sendRoapErrorStub);
3858
4121
  assert.calledWith(sendRoapErrorStub, {
3859
4122
  seq: 10,
3860
- errorType: MC.ErrorType.FAILED,
4123
+ errorType: ErrorType.FAILED,
3861
4124
  mediaId: meeting.mediaId,
3862
4125
  correlationId: meeting.correlationId,
3863
4126
  });
@@ -3893,6 +4156,83 @@ describe('plugin-meetings', () => {
3893
4156
  );
3894
4157
  done();
3895
4158
  });
4159
+
4160
+ it('listens to the breakouts changed event', () => {
4161
+ meeting.breakouts.updateBreakoutSessions = sinon.stub();
4162
+
4163
+ const payload = 'payload';
4164
+
4165
+ meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_MEETING_BREAKOUTS_CHANGED', payload);
4166
+
4167
+ assert.calledOnceWithExactly(meeting.breakouts.updateBreakoutSessions, payload);
4168
+ assert.calledWith(
4169
+ TriggerProxy.trigger,
4170
+ meeting,
4171
+ {file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
4172
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
4173
+ );
4174
+ });
4175
+ });
4176
+
4177
+ describe('#setUpBreakoutsListener', () => {
4178
+ it('listens to the closing event from breakouts and triggers the closing event', () => {
4179
+ TriggerProxy.trigger.reset();
4180
+ meeting.breakouts.trigger('BREAKOUTS_CLOSING');
4181
+
4182
+ assert.calledWith(
4183
+ TriggerProxy.trigger,
4184
+ meeting,
4185
+ {file: 'meeting/index', function: 'setUpBreakoutsListener'},
4186
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_CLOSING
4187
+ );
4188
+ });
4189
+
4190
+ it('listens to the message event from breakouts and triggers the message event', () => {
4191
+ TriggerProxy.trigger.reset();
4192
+
4193
+ const messageEvent = 'message';
4194
+
4195
+ meeting.breakouts.trigger('MESSAGE', messageEvent);
4196
+
4197
+ assert.calledWith(
4198
+ TriggerProxy.trigger,
4199
+ meeting,
4200
+ {file: 'meeting/index', function: 'setUpBreakoutsListener'},
4201
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_MESSAGE,
4202
+ messageEvent
4203
+ );
4204
+ });
4205
+
4206
+ it('listens to the members update event from breakouts and triggers the breakouts update event', () => {
4207
+ TriggerProxy.trigger.reset();
4208
+ meeting.breakouts.trigger('MEMBERS_UPDATE');
4209
+
4210
+ assert.calledWith(
4211
+ TriggerProxy.trigger,
4212
+ meeting,
4213
+ {file: 'meeting/index', function: 'setUpBreakoutsListener'},
4214
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
4215
+ );
4216
+ });
4217
+ });
4218
+
4219
+ describe('#setupLocusControlsListener', () => {
4220
+ it('listens to the locus breakouts update event', () => {
4221
+ const locus = {
4222
+ breakout: 'breakout'
4223
+ };
4224
+
4225
+ meeting.breakouts.updateBreakout = sinon.stub();
4226
+ meeting.locusInfo.emit({function: 'test', file: 'test'}, 'CONTROLS_MEETING_BREAKOUT_UPDATED', locus);
4227
+
4228
+ assert.calledOnceWithExactly(meeting.breakouts.updateBreakout, locus.breakout);
4229
+ assert.calledWith(
4230
+ TriggerProxy.trigger,
4231
+ meeting,
4232
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
4233
+ EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
4234
+ );
4235
+ });
3896
4236
  });
3897
4237
 
3898
4238
  describe('#setUpLocusUrlListener', () => {
@@ -3900,18 +4240,47 @@ describe('plugin-meetings', () => {
3900
4240
  const newLocusUrl = 'newLocusUrl/12345';
3901
4241
 
3902
4242
  meeting.members = {locusUrlUpdate: sinon.stub().returns(Promise.resolve(test1))};
4243
+ meeting.recordingController = {setLocusUrl: sinon.stub().returns(undefined)};
3903
4244
 
3904
- meeting.locusInfo.emit(
3905
- {function: 'test', file: 'test'},
3906
- 'LOCUS_INFO_UPDATE_URL',
4245
+ meeting.breakouts.locusUrlUpdate = sinon.stub();
4246
+
4247
+ meeting.locusInfo.emit({function: 'test', file: 'test'}, 'LOCUS_INFO_UPDATE_URL', newLocusUrl);
4248
+ assert.calledWith(
4249
+ meeting.members.locusUrlUpdate,
3907
4250
  newLocusUrl
3908
4251
  );
4252
+ assert.calledOnceWithExactly(meeting.breakouts.locusUrlUpdate, newLocusUrl);
3909
4253
  assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
4254
+ assert.calledWith(meeting.recordingController.setLocusUrl, newLocusUrl);
3910
4255
  assert.equal(meeting.locusUrl, newLocusUrl);
3911
4256
  assert(meeting.locusId, '12345');
3912
4257
  done();
3913
4258
  });
3914
4259
  });
4260
+
4261
+ describe('#setUpLocusServicesListener', () => {
4262
+ it('listens to the locus services update event', (done) => {
4263
+ const newLocusServices = {
4264
+ services: {
4265
+ record: {
4266
+ url: 'url',
4267
+ }
4268
+ },
4269
+ };
4270
+
4271
+ meeting.recordingController = {setServiceUrl: sinon.stub().returns(undefined), setSessionId: sinon.stub().returns(undefined)};
4272
+
4273
+ meeting.locusInfo.emit(
4274
+ {function: 'test', file: 'test'},
4275
+ 'LINKS_SERVICES',
4276
+ newLocusServices
4277
+ );
4278
+
4279
+ assert.calledWith(meeting.recordingController.setServiceUrl, newLocusServices.services.record.url);
4280
+ assert.calledOnce(meeting.recordingController.setSessionId);
4281
+ done();
4282
+ });
4283
+ });
3915
4284
  describe('#setUpLocusInfoMediaInactiveListener', () => {
3916
4285
  it('listens to disconnect due to un activity ', (done) => {
3917
4286
  TriggerProxy.trigger.reset();
@@ -4319,7 +4688,7 @@ describe('plugin-meetings', () => {
4319
4688
  let inMeetingActionsSetSpy;
4320
4689
  let canUserLockSpy;
4321
4690
  let canUserUnlockSpy;
4322
- let canUserRecordSpy;
4691
+ let canUserStartSpy;
4323
4692
  let canUserStopSpy;
4324
4693
  let canUserPauseSpy;
4325
4694
  let canUserResumeSpy;
@@ -4336,10 +4705,10 @@ describe('plugin-meetings', () => {
4336
4705
  locusInfoOnSpy = sinon.spy(meeting.locusInfo, 'on');
4337
4706
  canUserLockSpy = sinon.spy(MeetingUtil, 'canUserLock');
4338
4707
  canUserUnlockSpy = sinon.spy(MeetingUtil, 'canUserUnlock');
4339
- canUserRecordSpy = sinon.spy(MeetingUtil, 'canUserRecord');
4340
- canUserStopSpy = sinon.spy(MeetingUtil, 'canUserStop');
4341
- canUserPauseSpy = sinon.spy(MeetingUtil, 'canUserPause');
4342
- canUserResumeSpy = sinon.spy(MeetingUtil, 'canUserResume');
4708
+ canUserStartSpy = sinon.spy(RecordingUtil, 'canUserStart');
4709
+ canUserStopSpy = sinon.spy(RecordingUtil, 'canUserStop');
4710
+ canUserPauseSpy = sinon.spy(RecordingUtil, 'canUserPause');
4711
+ canUserResumeSpy = sinon.spy(RecordingUtil, 'canUserResume');
4343
4712
  inMeetingActionsSetSpy = sinon.spy(meeting.inMeetingActions, 'set');
4344
4713
  canUserRaiseHandSpy = sinon.spy(MeetingUtil, 'canUserRaiseHand');
4345
4714
  canUserLowerAllHandsSpy = sinon.spy(MeetingUtil, 'canUserLowerAllHands');
@@ -4381,7 +4750,7 @@ describe('plugin-meetings', () => {
4381
4750
 
4382
4751
  assert.calledWith(canUserLockSpy, payload.info.userDisplayHints);
4383
4752
  assert.calledWith(canUserUnlockSpy, payload.info.userDisplayHints);
4384
- assert.calledWith(canUserRecordSpy, payload.info.userDisplayHints);
4753
+ assert.calledWith(canUserStartSpy, payload.info.userDisplayHints);
4385
4754
  assert.calledWith(canUserStopSpy, payload.info.userDisplayHints);
4386
4755
  assert.calledWith(canUserPauseSpy, payload.info.userDisplayHints);
4387
4756
  assert.calledWith(canUserResumeSpy, payload.info.userDisplayHints);