@webex/plugin-meetings 3.7.0-next.22 → 3.7.0-next.24

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 (46) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +2 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/interpretation/index.js +1 -1
  6. package/dist/interpretation/siLanguage.js +1 -1
  7. package/dist/locus-info/index.js +8 -0
  8. package/dist/locus-info/index.js.map +1 -1
  9. package/dist/locus-info/selfUtils.js +20 -11
  10. package/dist/locus-info/selfUtils.js.map +1 -1
  11. package/dist/meeting/index.js +710 -645
  12. package/dist/meeting/index.js.map +1 -1
  13. package/dist/meeting/request.js +30 -0
  14. package/dist/meeting/request.js.map +1 -1
  15. package/dist/meeting/request.type.js.map +1 -1
  16. package/dist/member/index.js +9 -0
  17. package/dist/member/index.js.map +1 -1
  18. package/dist/member/types.js.map +1 -1
  19. package/dist/member/util.js +39 -28
  20. package/dist/member/util.js.map +1 -1
  21. package/dist/multistream/sendSlotManager.js +24 -0
  22. package/dist/multistream/sendSlotManager.js.map +1 -1
  23. package/dist/types/constants.d.ts +2 -0
  24. package/dist/types/meeting/index.d.ts +8 -0
  25. package/dist/types/meeting/request.d.ts +12 -1
  26. package/dist/types/meeting/request.type.d.ts +6 -0
  27. package/dist/types/member/index.d.ts +1 -0
  28. package/dist/types/member/types.d.ts +7 -0
  29. package/dist/types/multistream/sendSlotManager.d.ts +8 -1
  30. package/dist/webinar/index.js +1 -1
  31. package/package.json +3 -3
  32. package/src/constants.ts +2 -0
  33. package/src/locus-info/index.ts +13 -0
  34. package/src/locus-info/selfUtils.ts +6 -0
  35. package/src/meeting/index.ts +58 -0
  36. package/src/meeting/request.ts +26 -1
  37. package/src/meeting/request.type.ts +7 -0
  38. package/src/member/index.ts +9 -0
  39. package/src/member/types.ts +8 -0
  40. package/src/member/util.ts +34 -24
  41. package/src/multistream/sendSlotManager.ts +31 -0
  42. package/test/unit/spec/locus-info/index.js +93 -0
  43. package/test/unit/spec/locus-info/selfConstant.js +7 -0
  44. package/test/unit/spec/locus-info/selfUtils.js +39 -0
  45. package/test/unit/spec/meeting/index.js +108 -0
  46. package/test/unit/spec/member/util.js +52 -11
@@ -66,6 +66,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
66
66
  breakoutSessions: SelfUtils.getBreakoutSessions(self),
67
67
  breakout: SelfUtils.getBreakout(self),
68
68
  interpretation: SelfUtils.getInterpretation(self),
69
+ brb: SelfUtils.getBrb(self),
69
70
  };
70
71
  }
71
72
 
@@ -75,6 +76,7 @@ SelfUtils.parse = (self: any, deviceId: string) => {
75
76
  SelfUtils.getBreakoutSessions = (self) => self?.controls?.breakout?.sessions;
76
77
  SelfUtils.getBreakout = (self) => self?.controls?.breakout;
77
78
  SelfUtils.getInterpretation = (self) => self?.controls?.interpretation;
79
+ SelfUtils.getBrb = (self) => self?.controls?.brb;
78
80
 
79
81
  SelfUtils.getLayout = (self) =>
80
82
  Array.isArray(self?.controls?.layouts) ? self.controls.layouts[0].type : undefined;
@@ -128,6 +130,7 @@ SelfUtils.getSelves = (oldSelf, newSelf, deviceId) => {
128
130
  updates.isSharingBlockedChanged = previous?.isSharingBlocked !== current.isSharingBlocked;
129
131
  updates.breakoutsChanged = SelfUtils.breakoutsChanged(previous, current);
130
132
  updates.interpretationChanged = SelfUtils.interpretationChanged(previous, current);
133
+ updates.brbChanged = SelfUtils.brbChanged(previous, current);
131
134
 
132
135
  return {
133
136
  previous,
@@ -159,6 +162,9 @@ SelfUtils.breakoutsChanged = (previous, current) =>
159
162
  SelfUtils.interpretationChanged = (previous, current) =>
160
163
  !isEqual(previous?.interpretation, current?.interpretation) && !!current?.interpretation;
161
164
 
165
+ SelfUtils.brbChanged = (previous, current) =>
166
+ !isEqual(previous?.brb, current?.brb) && current?.brb !== undefined;
167
+
162
168
  SelfUtils.isMediaInactive = (previous, current) => {
163
169
  if (
164
170
  previous &&
@@ -3388,6 +3388,20 @@ export default class Meeting extends StatelessWebexPlugin {
3388
3388
  }
3389
3389
  });
3390
3390
 
3391
+ this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED, (payload) => {
3392
+ Trigger.trigger(
3393
+ this,
3394
+ {
3395
+ file: 'meeting/index',
3396
+ function: 'setUpLocusInfoSelfListener',
3397
+ },
3398
+ EVENT_TRIGGERS.MEETING_SELF_BRB_UPDATE,
3399
+ {
3400
+ payload,
3401
+ }
3402
+ );
3403
+ });
3404
+
3391
3405
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
3392
3406
  const isModeratorOrCohost =
3393
3407
  payload.newRoles?.includes(SELF_ROLES.MODERATOR) ||
@@ -3592,6 +3606,50 @@ export default class Meeting extends StatelessWebexPlugin {
3592
3606
  return this.members.admitMembers(memberIds, locusUrls);
3593
3607
  }
3594
3608
 
3609
+ /**
3610
+ * Manages be right back status updates for the current participant.
3611
+ *
3612
+ * @param {boolean} enabled - Indicates whether the user enabled brb or not.
3613
+ * @returns {Promise<void>} resolves when the brb status is updated or does nothing if not in a multistream meeting.
3614
+ * @throws {Error} - Throws an error if the request fails.
3615
+ */
3616
+ public async beRightBack(enabled: boolean): Promise<void> {
3617
+ if (!this.isMultistream) {
3618
+ const errorMessage = 'Meeting:index#beRightBack --> Not a multistream meeting';
3619
+ const error = new Error(errorMessage);
3620
+
3621
+ LoggerProxy.logger.error(error);
3622
+
3623
+ return Promise.reject(error);
3624
+ }
3625
+
3626
+ if (!this.mediaProperties.webrtcMediaConnection) {
3627
+ const errorMessage = 'Meeting:index#beRightBack --> WebRTC media connection is not defined';
3628
+ const error = new Error(errorMessage);
3629
+
3630
+ LoggerProxy.logger.error(error);
3631
+
3632
+ return Promise.reject(error);
3633
+ }
3634
+
3635
+ // this logic should be applied only to multistream meetings
3636
+ return this.meetingRequest
3637
+ .setBrb({
3638
+ enabled,
3639
+ locusUrl: this.locusUrl,
3640
+ deviceUrl: this.deviceUrl,
3641
+ selfId: this.selfId,
3642
+ })
3643
+ .then(() => {
3644
+ this.sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
3645
+ })
3646
+ .catch((error) => {
3647
+ LoggerProxy.logger.error('Meeting:index#beRightBack --> Error ', error);
3648
+
3649
+ return Promise.reject(error);
3650
+ });
3651
+ }
3652
+
3595
3653
  /**
3596
3654
  * Remove the member from the meeting, boot them
3597
3655
  * @param {String} memberId
@@ -27,7 +27,7 @@ import {
27
27
  _SLIDES_,
28
28
  ANNOTATION,
29
29
  } from '../constants';
30
- import {SendReactionOptions, ToggleReactionsOptions} from './request.type';
30
+ import {SendReactionOptions, BrbOptions, ToggleReactionsOptions} from './request.type';
31
31
  import MeetingUtil from './util';
32
32
  import {AnnotationInfo} from '../annotation/annotation.types';
33
33
  import {ClientMediaPreferences} from '../reachability/reachability.types';
@@ -909,4 +909,29 @@ export default class MeetingRequest extends StatelessWebexPlugin {
909
909
  uri: locusUrl,
910
910
  });
911
911
  }
912
+
913
+ /**
914
+ * Sends a request to set be right back status.
915
+ *
916
+ * @param {Object} options - The options for brb request.
917
+ * @param {boolean} options.enabled - Whether brb status is enabled.
918
+ * @param {string} options.locusUrl - The URL of the locus.
919
+ * @param {string} options.deviceUrl - The URL of the device.
920
+ * @param {string} options.selfId - The ID of the participant.
921
+ * @returns {Promise}
922
+ */
923
+ setBrb({enabled, locusUrl, deviceUrl, selfId}: BrbOptions) {
924
+ const uri = `${locusUrl}/${PARTICIPANT}/${selfId}/${CONTROLS}`;
925
+
926
+ return this.locusDeltaRequest({
927
+ method: HTTP_VERBS.PATCH,
928
+ uri,
929
+ body: {
930
+ brb: {
931
+ enabled,
932
+ deviceUrl,
933
+ },
934
+ },
935
+ });
936
+ }
912
937
  }
@@ -11,3 +11,10 @@ export type ToggleReactionsOptions = {
11
11
  locusUrl: string;
12
12
  requestingParticipantId: string;
13
13
  };
14
+
15
+ export type BrbOptions = {
16
+ enabled: boolean;
17
+ locusUrl: string;
18
+ deviceUrl: string;
19
+ selfId: string;
20
+ };
@@ -28,6 +28,7 @@ export default class Member {
28
28
  isRecording: any;
29
29
  isRemovable: any;
30
30
  isSelf: any;
31
+ isBrb: boolean;
31
32
  isUser: any;
32
33
  isVideoMuted: any;
33
34
  roles: IExternalRoles;
@@ -227,6 +228,13 @@ export default class Member {
227
228
  * @memberof Member
228
229
  */
229
230
  this.isRemovable = null;
231
+ /**
232
+ * @instance
233
+ * @type {Boolean}
234
+ * @public
235
+ * @memberof Member
236
+ */
237
+ this.isBrb = false;
230
238
  /**
231
239
  * @instance
232
240
  * @type {String}
@@ -295,6 +303,7 @@ export default class Member {
295
303
  this.supportsInterpretation = MemberUtil.isInterpretationSupported(participant);
296
304
  this.supportLiveAnnotation = MemberUtil.isLiveAnnotationSupported(participant);
297
305
  this.isGuest = MemberUtil.isGuest(participant);
306
+ this.isBrb = MemberUtil.isBrb(participant);
298
307
  this.isUser = MemberUtil.isUser(participant);
299
308
  this.isDevice = MemberUtil.isDevice(participant);
300
309
  this.isModerator = MemberUtil.isModerator(participant);
@@ -23,6 +23,14 @@ export type ParticipantWithRoles = {
23
23
  };
24
24
  };
25
25
 
26
+ export type ParticipantWithBrb = {
27
+ controls: {
28
+ brb?: {
29
+ enabled: boolean;
30
+ };
31
+ };
32
+ };
33
+
26
34
  // values are inherited from locus so don't update these
27
35
  export enum MediaStatus {
28
36
  RECVONLY = 'RECVONLY', // participant only receiving and not sending
@@ -4,6 +4,7 @@ import {
4
4
  ServerRoles,
5
5
  ServerRoleShape,
6
6
  IMediaStatus,
7
+ ParticipantWithBrb,
7
8
  } from './types';
8
9
  import {
9
10
  _USER_,
@@ -29,7 +30,7 @@ import ParameterError from '../common/errors/parameter';
29
30
  const MemberUtil: any = {};
30
31
 
31
32
  /**
32
- * @param {Object} participant the locus participant
33
+ * @param {Object} participant - The locus participant object.
33
34
  * @returns {Boolean}
34
35
  */
35
36
  MemberUtil.canReclaimHost = (participant) => {
@@ -43,14 +44,23 @@ MemberUtil.canReclaimHost = (participant) => {
43
44
  };
44
45
 
45
46
  /**
46
- * @param {Object} participant the locus participant
47
+ * @param {Object} participant - The locus participant object.
47
48
  * @returns {[ServerRoleShape]}
48
49
  */
49
50
  MemberUtil.getControlsRoles = (participant: ParticipantWithRoles): Array<ServerRoleShape> =>
50
51
  participant?.controls?.role?.roles;
51
52
 
52
53
  /**
53
- * @param {Object} participant the locus participant
54
+ * Checks if the participant has the brb status enabled.
55
+ *
56
+ * @param {ParticipantWithBrb} participant - The locus participant object.
57
+ * @returns {boolean} - True if the participant has brb enabled, false otherwise.
58
+ */
59
+ MemberUtil.isBrb = (participant: ParticipantWithBrb): boolean =>
60
+ participant.controls?.brb?.enabled || false;
61
+
62
+ /**
63
+ * @param {Object} participant - The locus participant object.
54
64
  * @param {ServerRoles} controlRole the search role
55
65
  * @returns {Boolean}
56
66
  */
@@ -60,28 +70,28 @@ MemberUtil.hasRole = (participant: any, controlRole: ServerRoles): boolean =>
60
70
  );
61
71
 
62
72
  /**
63
- * @param {Object} participant the locus participant
73
+ * @param {Object} participant - The locus participant object.
64
74
  * @returns {Boolean}
65
75
  */
66
76
  MemberUtil.hasCohost = (participant: ParticipantWithRoles): boolean =>
67
77
  MemberUtil.hasRole(participant, ServerRoles.Cohost) || false;
68
78
 
69
79
  /**
70
- * @param {Object} participant the locus participant
80
+ * @param {Object} participant - The locus participant object.
71
81
  * @returns {Boolean}
72
82
  */
73
83
  MemberUtil.hasModerator = (participant: ParticipantWithRoles): boolean =>
74
84
  MemberUtil.hasRole(participant, ServerRoles.Moderator) || false;
75
85
 
76
86
  /**
77
- * @param {Object} participant the locus participant
87
+ * @param {Object} participant - The locus participant object.
78
88
  * @returns {Boolean}
79
89
  */
80
90
  MemberUtil.hasPresenter = (participant: ParticipantWithRoles): boolean =>
81
91
  MemberUtil.hasRole(participant, ServerRoles.Presenter) || false;
82
92
 
83
93
  /**
84
- * @param {Object} participant the locus participant
94
+ * @param {Object} participant - The locus participant object.
85
95
  * @returns {IExternalRoles}
86
96
  */
87
97
  MemberUtil.extractControlRoles = (participant: ParticipantWithRoles): IExternalRoles => {
@@ -95,7 +105,7 @@ MemberUtil.extractControlRoles = (participant: ParticipantWithRoles): IExternalR
95
105
  };
96
106
 
97
107
  /**
98
- * @param {Object} participant the locus participant
108
+ * @param {Object} participant - The locus participant object.
99
109
  * @returns {Boolean}
100
110
  */
101
111
  MemberUtil.isUser = (participant: any) => participant && participant.type === _USER_;
@@ -103,13 +113,13 @@ MemberUtil.isUser = (participant: any) => participant && participant.type === _U
103
113
  MemberUtil.isModerator = (participant) => participant && participant.moderator;
104
114
 
105
115
  /**
106
- * @param {Object} participant the locus participant
116
+ * @param {Object} participant - The locus participant object.
107
117
  * @returns {Boolean}
108
118
  */
109
119
  MemberUtil.isGuest = (participant: any) => participant && participant.guest;
110
120
 
111
121
  /**
112
- * @param {Object} participant the locus participant
122
+ * @param {Object} participant - The locus participant object.
113
123
  * @returns {Boolean}
114
124
  */
115
125
  MemberUtil.isDevice = (participant: any) => participant && participant.type === _RESOURCE_ROOM_;
@@ -120,7 +130,7 @@ MemberUtil.isModeratorAssignmentProhibited = (participant) =>
120
130
  /**
121
131
  * checks to see if the participant id is the same as the passed id
122
132
  * there are multiple ids that can be used
123
- * @param {Object} participant the locus participant
133
+ * @param {Object} participant - The locus participant object.
124
134
  * @param {String} id
125
135
  * @returns {Boolean}
126
136
  */
@@ -130,7 +140,7 @@ MemberUtil.isSame = (participant: any, id: string) =>
130
140
  /**
131
141
  * checks to see if the participant id is the same as the passed id for associated devices
132
142
  * there are multiple ids that can be used
133
- * @param {Object} participant the locus participant
143
+ * @param {Object} participant - The locus participant object.
134
144
  * @param {String} id
135
145
  * @returns {Boolean}
136
146
  */
@@ -142,7 +152,7 @@ MemberUtil.isAssociatedSame = (participant: any, id: string) =>
142
152
  );
143
153
 
144
154
  /**
145
- * @param {Object} participant the locus participant
155
+ * @param {Object} participant - The locus participant object.
146
156
  * @param {Boolean} isGuest
147
157
  * @param {String} status
148
158
  * @returns {Boolean}
@@ -161,7 +171,7 @@ MemberUtil.isNotAdmitted = (participant: any, isGuest: boolean, status: string):
161
171
  !status === _IN_MEETING_);
162
172
 
163
173
  /**
164
- * @param {Object} participant the locus participant
174
+ * @param {Object} participant - The locus participant object.
165
175
  * @returns {Boolean}
166
176
  */
167
177
  MemberUtil.isAudioMuted = (participant: any) => {
@@ -173,7 +183,7 @@ MemberUtil.isAudioMuted = (participant: any) => {
173
183
  };
174
184
 
175
185
  /**
176
- * @param {Object} participant the locus participant
186
+ * @param {Object} participant - The locus participant object.
177
187
  * @returns {Boolean}
178
188
  */
179
189
  MemberUtil.isVideoMuted = (participant: any): boolean => {
@@ -185,7 +195,7 @@ MemberUtil.isVideoMuted = (participant: any): boolean => {
185
195
  };
186
196
 
187
197
  /**
188
- * @param {Object} participant the locus participant
198
+ * @param {Object} participant - The locus participant object.
189
199
  * @returns {Boolean}
190
200
  */
191
201
  MemberUtil.isHandRaised = (participant: any) => {
@@ -197,7 +207,7 @@ MemberUtil.isHandRaised = (participant: any) => {
197
207
  };
198
208
 
199
209
  /**
200
- * @param {Object} participant the locus participant
210
+ * @param {Object} participant - The locus participant object.
201
211
  * @returns {Boolean}
202
212
  */
203
213
  MemberUtil.isBreakoutsSupported = (participant) => {
@@ -209,7 +219,7 @@ MemberUtil.isBreakoutsSupported = (participant) => {
209
219
  };
210
220
 
211
221
  /**
212
- * @param {Object} participant the locus participant
222
+ * @param {Object} participant - The locus participant object.
213
223
  * @returns {Boolean}
214
224
  */
215
225
  MemberUtil.isInterpretationSupported = (participant) => {
@@ -223,7 +233,7 @@ MemberUtil.isInterpretationSupported = (participant) => {
223
233
  };
224
234
 
225
235
  /**
226
- * @param {Object} participant the locus participant
236
+ * @param {Object} participant - The locus participant object.
227
237
  * @returns {Boolean}
228
238
  */
229
239
  MemberUtil.isLiveAnnotationSupported = (participant) => {
@@ -279,7 +289,7 @@ MemberUtil.getRecordingMember = (controls: any) => {
279
289
  };
280
290
 
281
291
  /**
282
- * @param {Object} participant the locus participant
292
+ * @param {Object} participant - The locus participant object.
283
293
  * @returns {Boolean}
284
294
  */
285
295
  MemberUtil.isRecording = (participant: any) => {
@@ -325,7 +335,7 @@ MemberUtil.isMutable = (isSelf, isDevice, isInMeeting, isMuted, type) => {
325
335
  };
326
336
 
327
337
  /**
328
- * @param {Object} participant the locus participant
338
+ * @param {Object} participant - The locus participant object.
329
339
  * @returns {String}
330
340
  */
331
341
  MemberUtil.extractStatus = (participant: any) => {
@@ -355,7 +365,7 @@ MemberUtil.extractStatus = (participant: any) => {
355
365
  };
356
366
 
357
367
  /**
358
- * @param {Object} participant the locus participant
368
+ * @param {Object} participant - The locus participant object.
359
369
  * @returns {String}
360
370
  */
361
371
  MemberUtil.extractId = (participant: any) => {
@@ -368,7 +378,7 @@ MemberUtil.extractId = (participant: any) => {
368
378
 
369
379
  /**
370
380
  * extracts the media status from nested participant object
371
- * @param {Object} participant the locus participant
381
+ * @param {Object} participant - The locus participant object.
372
382
  * @returns {Object}
373
383
  */
374
384
  MemberUtil.extractMediaStatus = (participant: any): IMediaStatus => {
@@ -383,7 +393,7 @@ MemberUtil.extractMediaStatus = (participant: any): IMediaStatus => {
383
393
  };
384
394
 
385
395
  /**
386
- * @param {Object} participant the locus participant
396
+ * @param {Object} participant - The locus participant object.
387
397
  * @returns {String}
388
398
  */
389
399
  MemberUtil.extractName = (participant: any) => {
@@ -4,6 +4,7 @@ import {
4
4
  LocalStream,
5
5
  MultistreamRoapMediaConnection,
6
6
  NamedMediaGroup,
7
+ StreamState,
7
8
  } from '@webex/internal-media-core';
8
9
 
9
10
  export default class SendSlotManager {
@@ -83,6 +84,36 @@ export default class SendSlotManager {
83
84
  );
84
85
  }
85
86
 
87
+ /**
88
+ * Sets the source state override for the given media type.
89
+ * @param {MediaType} mediaType - The type of media (must be MediaType.VideoMain to apply source state changes).
90
+ * @param {StreamState | null} state - The state to set or null to clear the override value.
91
+ * @returns {void}
92
+ */
93
+ public setSourceStateOverride(mediaType: MediaType, state: StreamState | null) {
94
+ if (mediaType !== MediaType.VideoMain) {
95
+ throw new Error(
96
+ `sendSlotManager cannot set source state override which media type is ${mediaType}`
97
+ );
98
+ }
99
+
100
+ const slot = this.slots.get(mediaType);
101
+
102
+ if (!slot) {
103
+ throw new Error(`Slot for ${mediaType} does not exist`);
104
+ }
105
+
106
+ if (state) {
107
+ slot.setSourceStateOverride(state);
108
+ } else {
109
+ slot.clearSourceStateOverride();
110
+ }
111
+
112
+ this.LoggerProxy.logger.info(
113
+ `SendSlotsManager->setSourceStateOverride#set source state override for ${mediaType} to ${state}`
114
+ );
115
+ }
116
+
86
117
  /**
87
118
  * This method publishes the given stream to the sendSlot for the given mediaType
88
119
  * @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
@@ -794,6 +794,75 @@ describe('plugin-meetings', () => {
794
794
  });
795
795
 
796
796
  describe('#updateSelf', () => {
797
+ it('should trigger SELF_MEETING_BRB_CHANGED when brb state changed', () => {
798
+ locusInfo.self = undefined;
799
+
800
+ const assertBrb = (enabled) => {
801
+ const selfWithBrbChanged = cloneDeep(self);
802
+ selfWithBrbChanged.controls.brb = enabled;
803
+
804
+ locusInfo.emitScoped = sinon.stub();
805
+ locusInfo.updateSelf(selfWithBrbChanged, []);
806
+
807
+ assert.calledWith(
808
+ locusInfo.emitScoped,
809
+ {file: 'locus-info', function: 'updateSelf'},
810
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
811
+ {brb: enabled}
812
+ );
813
+ };
814
+
815
+ assertBrb(true);
816
+ assertBrb(false);
817
+ });
818
+
819
+ it('should not trigger SELF_MEETING_BRB_CHANGED when brb state did not change', () => {
820
+ const assertBrbUnchanged = (value) => {
821
+ locusInfo.self = undefined;
822
+
823
+ const selfWithBrbChanged = cloneDeep(self);
824
+ selfWithBrbChanged.controls.brb = value;
825
+ locusInfo.self = selfWithBrbChanged;
826
+
827
+ locusInfo.emitScoped = sinon.stub();
828
+
829
+ const newSelf = cloneDeep(self);
830
+ newSelf.controls.brb = value;
831
+
832
+ locusInfo.updateSelf(newSelf, []);
833
+
834
+ assert.neverCalledWith(
835
+ locusInfo.emitScoped,
836
+ {file: 'locus-info', function: 'updateSelf'},
837
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
838
+ {brb: value}
839
+ );
840
+ };
841
+
842
+ assertBrbUnchanged(true);
843
+ assertBrbUnchanged(false);
844
+ });
845
+
846
+ it('should not trigger SELF_MEETING_BRB_CHANGED when brb state is undefined', () => {
847
+ const selfWithBrbChanged = cloneDeep(self);
848
+ selfWithBrbChanged.controls.brb = false;
849
+ locusInfo.self = selfWithBrbChanged;
850
+
851
+ locusInfo.emitScoped = sinon.stub();
852
+
853
+ const newSelf = cloneDeep(self);
854
+ newSelf.controls.brb = undefined;
855
+
856
+ locusInfo.updateSelf(newSelf, []);
857
+
858
+ assert.neverCalledWith(
859
+ locusInfo.emitScoped,
860
+ {file: 'locus-info', function: 'updateSelf'},
861
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
862
+ {brb: undefined}
863
+ );
864
+ });
865
+
797
866
  it('should trigger CONTROLS_MEETING_LAYOUT_UPDATED when the meeting layout controls change', () => {
798
867
  const layoutType = 'EXAMPLE TYPE';
799
868
 
@@ -1389,6 +1458,30 @@ describe('plugin-meetings', () => {
1389
1458
  }
1390
1459
  );
1391
1460
  });
1461
+
1462
+ it('should not trigger any events if controls is undefined', () => {
1463
+ locusInfo.self = self;
1464
+ locusInfo.emitScoped = sinon.stub();
1465
+ const newSelf = cloneDeep(self);
1466
+ newSelf.controls = undefined;
1467
+
1468
+ locusInfo.updateSelf(newSelf, []);
1469
+
1470
+ const eventsSet = new Set([
1471
+ LOCUSINFO.EVENTS.CONTROLS_MEETING_LAYOUT_UPDATED,
1472
+ LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED,
1473
+ LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
1474
+ LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED,
1475
+ LOCUSINFO.EVENTS.LOCAL_UNMUTE_REQUIRED,
1476
+ LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
1477
+ ]);
1478
+
1479
+ // check all events that contain logic on controls existence
1480
+ locusInfo.emitScoped.getCalls().forEach((call) => {
1481
+ const eventName = call.args[1];
1482
+ assert.isFalse(eventsSet.has(eventName));
1483
+ });
1484
+ });
1392
1485
  });
1393
1486
 
1394
1487
  describe('#updateMeetingInfo', () => {
@@ -304,6 +304,13 @@ export const selfWithInactivity = {
304
304
  localRecord: {
305
305
  recording: false,
306
306
  },
307
+ brb: {
308
+ enabled: true,
309
+ meta: {
310
+ lastModified: '2024-10-24T14:05:58.526Z',
311
+ modifiedBy: '70978427-8238-4ffc-9227-8baf4b80b831',
312
+ },
313
+ },
307
314
  layouts: [
308
315
  {
309
316
  type: 'activePresence',
@@ -60,6 +60,14 @@ describe('plugin-meetings', () => {
60
60
  assert.calledWith(spy, self);
61
61
  assert.deepEqual(parsedSelf.layout, self.controls.layouts[0].type);
62
62
  });
63
+
64
+ it('calls getBrb and returns the resulting brb value', () => {
65
+ const spy = Sinon.spy(SelfUtils, 'getBrb');
66
+ const parsedSelf = SelfUtils.parse(self);
67
+
68
+ assert.calledWith(spy, self);
69
+ assert.deepEqual(parsedSelf.brb, self.controls.brb);
70
+ });
63
71
  });
64
72
 
65
73
  describe('getLayout', () => {
@@ -170,6 +178,37 @@ describe('plugin-meetings', () => {
170
178
  });
171
179
  });
172
180
 
181
+ describe('brbChanged', () => {
182
+ it('should return true if brb have changed', () => {
183
+ const current = {
184
+ brb: {enabled: true}
185
+ };
186
+ const previous = {
187
+ brb: {enabled: false}
188
+ };
189
+
190
+ assert.isTrue(SelfUtils.brbChanged(previous, current));
191
+ });
192
+
193
+ it('should return false if brb have not changed', () => {
194
+ const current = {
195
+ brb: {enabled: true}
196
+ };
197
+ const previous = {
198
+ brb: {enabled: true}
199
+ };
200
+
201
+ assert.isFalse(SelfUtils.brbChanged(previous, current));
202
+ });
203
+
204
+ it('should return false if brb in current is undefined', () => {
205
+ const current = {};
206
+ const previous = {brb: {enabled: true}};
207
+
208
+ assert.isFalse(SelfUtils.brbChanged(previous, current));
209
+ });
210
+ });
211
+
173
212
  describe('canNotViewTheParticipantList', () => {
174
213
  it('should return the correct value', () => {
175
214
  assert.equal(