@webex/plugin-meetings 3.12.0-next.47 → 3.12.0-next.49

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.
@@ -1,10 +1,7 @@
1
1
  import {assert} from '@webex/test-helper-chai';
2
2
  import ControlsUtils from '@webex/plugin-meetings/src/locus-info/controlsUtils';
3
- import controlsUtils from "@webex/plugin-meetings/src/locus-info/controlsUtils";
4
- import {
5
- MEETING_STATE,
6
- BREAKOUTS,
7
- } from '../../../../src/constants';
3
+ import controlsUtils from '@webex/plugin-meetings/src/locus-info/controlsUtils';
4
+ import {MEETING_STATE, BREAKOUTS} from '../../../../src/constants';
8
5
 
9
6
  const defaultControls = {
10
7
  entryExitTone: {
@@ -82,17 +79,31 @@ describe('plugin-meetings', () => {
82
79
 
83
80
  const parsedControls = ControlsUtils.parse(newControls);
84
81
 
85
- assert.equal(parsedControls.reactions.showDisplayNameWithReactions, newControls.reactions.showDisplayNameWithReactions);
82
+ assert.equal(
83
+ parsedControls.reactions.showDisplayNameWithReactions,
84
+ newControls.reactions.showDisplayNameWithReactions
85
+ );
86
86
  });
87
87
 
88
88
  it('should parse the viewTheParticipantList control', () => {
89
- const newControls = {viewTheParticipantList: {enabled: true, panelistEnabled: true, attendeeCount: false}};
89
+ const newControls = {
90
+ viewTheParticipantList: {enabled: true, panelistEnabled: true, attendeeCount: false},
91
+ };
90
92
 
91
93
  const parsedControls = ControlsUtils.parse(newControls);
92
94
 
93
- assert.equal(parsedControls.viewTheParticipantList.enabled, newControls.viewTheParticipantList.enabled);
94
- assert.equal(parsedControls.viewTheParticipantList.panelistEnabled, newControls.viewTheParticipantList.panelistEnabled);
95
- assert.equal(parsedControls.viewTheParticipantList.attendeeCount, newControls.viewTheParticipantList.attendeeCount);
95
+ assert.equal(
96
+ parsedControls.viewTheParticipantList.enabled,
97
+ newControls.viewTheParticipantList.enabled
98
+ );
99
+ assert.equal(
100
+ parsedControls.viewTheParticipantList.panelistEnabled,
101
+ newControls.viewTheParticipantList.panelistEnabled
102
+ );
103
+ assert.equal(
104
+ parsedControls.viewTheParticipantList.attendeeCount,
105
+ newControls.viewTheParticipantList.attendeeCount
106
+ );
96
107
  });
97
108
 
98
109
  it('should parse the raiseHand control', () => {
@@ -125,7 +136,10 @@ describe('plugin-meetings', () => {
125
136
  const parsedControls = ControlsUtils.parse(newControls);
126
137
 
127
138
  assert.equal(parsedControls.meetingFull.meetingFull, newControls.meetingFull.meetingFull);
128
- assert.equal(parsedControls.meetingFull.meetingPanelistFull, newControls.meetingFull.meetingPanelistFull);
139
+ assert.equal(
140
+ parsedControls.meetingFull.meetingPanelistFull,
141
+ newControls.meetingFull.meetingPanelistFull
142
+ );
129
143
  });
130
144
 
131
145
  it('should parse the practiceSession control', () => {
@@ -137,13 +151,28 @@ describe('plugin-meetings', () => {
137
151
  });
138
152
 
139
153
  it('should parse the videoLayout control', () => {
140
- const newControls = {videoLayout: {overrideDefault: true, lockAttendeeViewOnStageOnly:false, stageParameters: {}}};
154
+ const newControls = {
155
+ videoLayout: {
156
+ overrideDefault: true,
157
+ lockAttendeeViewOnStageOnly: false,
158
+ stageParameters: {},
159
+ },
160
+ };
141
161
 
142
162
  const parsedControls = ControlsUtils.parse(newControls);
143
163
 
144
- assert.equal(parsedControls.videoLayout.overrideDefault, newControls.videoLayout.overrideDefault);
145
- assert.equal(parsedControls.videoLayout.lockAttendeeViewOnStageOnly, newControls.videoLayout.lockAttendeeViewOnStageOnly);
146
- assert.equal(parsedControls.videoLayout.stageParameters, newControls.videoLayout.stageParameters);
164
+ assert.equal(
165
+ parsedControls.videoLayout.overrideDefault,
166
+ newControls.videoLayout.overrideDefault
167
+ );
168
+ assert.equal(
169
+ parsedControls.videoLayout.lockAttendeeViewOnStageOnly,
170
+ newControls.videoLayout.lockAttendeeViewOnStageOnly
171
+ );
172
+ assert.equal(
173
+ parsedControls.videoLayout.stageParameters,
174
+ newControls.videoLayout.stageParameters
175
+ );
147
176
  });
148
177
 
149
178
  it('should parse the annotationControl control', () => {
@@ -174,13 +203,77 @@ describe('plugin-meetings', () => {
174
203
  });
175
204
 
176
205
  it('should parse the hesiodLlmId in transcribe control', () => {
177
- const newControls = {transcribe: {hesiodLlmId: 'llm-123', transcribing: true, caption: true, spokenLanguage: 'en-US'}};
206
+ const newControls = {
207
+ transcribe: {
208
+ hesiodLlmId: 'llm-123',
209
+ transcribing: true,
210
+ caption: true,
211
+ spokenLanguage: 'en-US',
212
+ },
213
+ };
178
214
 
179
215
  const parsedControls = ControlsUtils.parse(newControls);
180
216
 
181
217
  assert.equal(parsedControls.transcribe.hesiodLlmId, newControls.transcribe.hesiodLlmId);
182
218
  });
183
219
 
220
+ it('should parse modifiedByServiceAppName from record meta when present', () => {
221
+ const newControls = {
222
+ record: {
223
+ recording: true,
224
+ paused: false,
225
+ meta: {lastModified: '2026-01-01', modifiedByServiceAppName: 'My Bot'},
226
+ },
227
+ };
228
+
229
+ const parsedControls = ControlsUtils.parse(newControls);
230
+
231
+ assert.equal(parsedControls.record.modifiedByServiceAppName, 'My Bot');
232
+ });
233
+
234
+ it('should parse modifiedByServiceAppId from record meta when present', () => {
235
+ const newControls = {
236
+ record: {
237
+ recording: true,
238
+ paused: false,
239
+ meta: {lastModified: '2026-01-01', modifiedByServiceAppId: 'app-id-123'},
240
+ },
241
+ };
242
+
243
+ const parsedControls = ControlsUtils.parse(newControls);
244
+
245
+ assert.equal(parsedControls.record.modifiedByServiceAppId, 'app-id-123');
246
+ });
247
+
248
+ it('should return undefined for modifiedByServiceAppName and modifiedByServiceAppId when absent from record meta', () => {
249
+ const newControls = {
250
+ record: {recording: true, paused: false, meta: {lastModified: '2026-01-01'}},
251
+ };
252
+
253
+ const parsedControls = ControlsUtils.parse(newControls);
254
+
255
+ assert.isUndefined(parsedControls.record.modifiedByServiceAppName);
256
+ assert.isUndefined(parsedControls.record.modifiedByServiceAppId);
257
+ // existing fields still parsed correctly
258
+ assert.equal(parsedControls.record.recording, true);
259
+ assert.equal(parsedControls.record.paused, false);
260
+ assert.equal(parsedControls.record.lastModified, '2026-01-01');
261
+ });
262
+
263
+ it('should handle record with no meta without throwing', () => {
264
+ const newControls = {
265
+ record: {recording: true, paused: false},
266
+ };
267
+
268
+ const parsedControls = ControlsUtils.parse(newControls);
269
+
270
+ assert.equal(parsedControls.record.recording, true);
271
+ assert.equal(parsedControls.record.paused, false);
272
+ assert.isUndefined(parsedControls.record.lastModified);
273
+ assert.isUndefined(parsedControls.record.modifiedByServiceAppName);
274
+ assert.isUndefined(parsedControls.record.modifiedByServiceAppId);
275
+ });
276
+
184
277
  describe('videoEnabled', () => {
185
278
  it('returns expected', () => {
186
279
  const result = ControlsUtils.parse({video: {enabled: true}});
@@ -246,19 +339,29 @@ describe('plugin-meetings', () => {
246
339
  });
247
340
 
248
341
  it('returns hasViewTheParticipantListChanged = true when changed', () => {
249
- const oldControls = {viewTheParticipantList: {enabled: true, panelistEnabled: true, attendeeCount: false}};
342
+ const oldControls = {
343
+ viewTheParticipantList: {enabled: true, panelistEnabled: true, attendeeCount: false},
344
+ };
250
345
 
251
- let result = ControlsUtils.getControls(oldControls, {viewTheParticipantList: {enabled: false, panelistEnabled: true, attendeeCount: false}});
346
+ let result = ControlsUtils.getControls(oldControls, {
347
+ viewTheParticipantList: {enabled: false, panelistEnabled: true, attendeeCount: false},
348
+ });
252
349
 
253
350
  assert.equal(result.updates.hasViewTheParticipantListChanged, true);
254
351
 
255
- result = ControlsUtils.getControls(oldControls, {viewTheParticipantList: {enabled: true, panelistEnabled: false, attendeeCount: false}});
352
+ result = ControlsUtils.getControls(oldControls, {
353
+ viewTheParticipantList: {enabled: true, panelistEnabled: false, attendeeCount: false},
354
+ });
256
355
 
257
356
  assert.equal(result.updates.hasViewTheParticipantListChanged, true);
258
- result = ControlsUtils.getControls(oldControls, {viewTheParticipantList: {enabled: true, panelistEnabled: true, attendeeCount: true}});
357
+ result = ControlsUtils.getControls(oldControls, {
358
+ viewTheParticipantList: {enabled: true, panelistEnabled: true, attendeeCount: true},
359
+ });
259
360
 
260
361
  assert.equal(result.updates.hasViewTheParticipantListChanged, true);
261
- result = ControlsUtils.getControls(oldControls, {viewTheParticipantList: {enabled: true, panelistEnabled: true, attendeeCount: false}});
362
+ result = ControlsUtils.getControls(oldControls, {
363
+ viewTheParticipantList: {enabled: true, panelistEnabled: true, attendeeCount: false},
364
+ });
262
365
 
263
366
  assert.equal(result.updates.hasViewTheParticipantListChanged, false);
264
367
  });
@@ -294,7 +397,9 @@ describe('plugin-meetings', () => {
294
397
 
295
398
  assert.equal(result.updates.hasMeetingFullChanged, true);
296
399
 
297
- result = ControlsUtils.getControls(newControls, {meetingFull: {meetingFull: true, meetingPanelistFull: true}});
400
+ result = ControlsUtils.getControls(newControls, {
401
+ meetingFull: {meetingFull: true, meetingPanelistFull: true},
402
+ });
298
403
 
299
404
  assert.equal(result.updates.hasMeetingFullChanged, true);
300
405
  });
@@ -386,7 +491,10 @@ describe('plugin-meetings', () => {
386
491
  interpretation: 'interpretation',
387
492
  };
388
493
 
389
- const {updates} = ControlsUtils.getControls({interpretation: 'interpretation'}, newControls);
494
+ const {updates} = ControlsUtils.getControls(
495
+ {interpretation: 'interpretation'},
496
+ newControls
497
+ );
390
498
 
391
499
  assert.equal(updates.hasInterpretationChanged, false);
392
500
  });
@@ -396,7 +504,10 @@ describe('plugin-meetings', () => {
396
504
  manualCaptionControl: {enabled: false},
397
505
  };
398
506
 
399
- const {updates} = ControlsUtils.getControls({manualCaptionControl: {enabled: true}}, newControls);
507
+ const {updates} = ControlsUtils.getControls(
508
+ {manualCaptionControl: {enabled: true}},
509
+ newControls
510
+ );
400
511
 
401
512
  assert.equal(updates.hasManualCaptionChanged, true);
402
513
  });
@@ -406,7 +517,10 @@ describe('plugin-meetings', () => {
406
517
  manualCaptionControl: {enabled: true},
407
518
  };
408
519
 
409
- const {updates} = ControlsUtils.getControls({manualCaptionControl: {enabled: true}}, newControls);
520
+ const {updates} = ControlsUtils.getControls(
521
+ {manualCaptionControl: {enabled: true}},
522
+ newControls
523
+ );
410
524
 
411
525
  assert.equal(updates.hasManualCaptionChanged, false);
412
526
  });
@@ -436,8 +550,8 @@ describe('plugin-meetings', () => {
436
550
  });
437
551
 
438
552
  it('returns false when previous spoken language is undefined and current is a invalid value', () => {
439
- const previous = { transcribe: undefined };
440
- const current = { transcribe: { spokenLanguage: null } };
553
+ const previous = {transcribe: undefined};
554
+ const current = {transcribe: {spokenLanguage: null}};
441
555
 
442
556
  const {updates} = ControlsUtils.getControls(previous, current);
443
557
 
@@ -445,8 +559,8 @@ describe('plugin-meetings', () => {
445
559
  });
446
560
 
447
561
  it('detects spoken language change when previous is undefined and current is a valid value', () => {
448
- const previous = { transcribe: undefined };
449
- const current = { transcribe: { spokenLanguage: 'en-US' } };
562
+ const previous = {transcribe: undefined};
563
+ const current = {transcribe: {spokenLanguage: 'en-US'}};
450
564
 
451
565
  const {updates} = ControlsUtils.getControls(previous, current);
452
566
 
@@ -454,8 +568,8 @@ describe('plugin-meetings', () => {
454
568
  });
455
569
 
456
570
  it('returns false when spoken language changes to a same value', () => {
457
- const previous = { transcribe: {caption: true, spokenLanguage: 'en-US' } };
458
- const current = { transcribe: {caption: true, spokenLanguage: 'en-US' } };
571
+ const previous = {transcribe: {caption: true, spokenLanguage: 'en-US'}};
572
+ const current = {transcribe: {caption: true, spokenLanguage: 'en-US'}};
459
573
 
460
574
  const {updates} = ControlsUtils.getControls(previous, current);
461
575
 
@@ -463,8 +577,8 @@ describe('plugin-meetings', () => {
463
577
  });
464
578
 
465
579
  it('returns true when spoken language changes to a different value', () => {
466
- const previous = { transcribe: {caption: true, spokenLanguage: 'en-US' } };
467
- const current = { transcribe: {caption: true, spokenLanguage: 'fr-FR' } };
580
+ const previous = {transcribe: {caption: true, spokenLanguage: 'en-US'}};
581
+ const current = {transcribe: {caption: true, spokenLanguage: 'fr-FR'}};
468
582
 
469
583
  const {updates} = ControlsUtils.getControls(previous, current);
470
584
 
@@ -472,8 +586,8 @@ describe('plugin-meetings', () => {
472
586
  });
473
587
 
474
588
  it('returns false when previous hesiodLlmId is undefined and current is a invalid value', () => {
475
- const previous = { transcribe: undefined };
476
- const current = { transcribe: { hesiodLlmId: null } };
589
+ const previous = {transcribe: undefined};
590
+ const current = {transcribe: {hesiodLlmId: null}};
477
591
 
478
592
  const {updates} = ControlsUtils.getControls(previous, current);
479
593
 
@@ -481,8 +595,8 @@ describe('plugin-meetings', () => {
481
595
  });
482
596
 
483
597
  it('detects hesiodLlmId change when previous is undefined and current is a valid value', () => {
484
- const previous = { transcribe: undefined };
485
- const current = { transcribe: { hesiodLlmId: '123a-456b' } };
598
+ const previous = {transcribe: undefined};
599
+ const current = {transcribe: {hesiodLlmId: '123a-456b'}};
486
600
 
487
601
  const {updates} = ControlsUtils.getControls(previous, current);
488
602
 
@@ -512,7 +626,9 @@ describe('plugin-meetings', () => {
512
626
  });
513
627
 
514
628
  it('parses aiSummaryNotification into the transcribe block', () => {
515
- const controls = {transcribe: {transcribing: false, caption: false, aiSummaryNotification: true}};
629
+ const controls = {
630
+ transcribe: {transcribing: false, caption: false, aiSummaryNotification: true},
631
+ };
516
632
  const parsed = ControlsUtils.parse(controls);
517
633
  assert.equal(parsed.transcribe.aiSummaryNotification, true);
518
634
  });
@@ -580,7 +696,8 @@ describe('plugin-meetings', () => {
580
696
  const oldLocus = {};
581
697
  const newLocus = {};
582
698
  assert.deepEqual(controlsUtils.getSessionSwitchStatus(oldLocus, newLocus), {
583
- isReturnToMain: false, isJoinToBreakout: false
699
+ isReturnToMain: false,
700
+ isJoinToBreakout: false,
584
701
  });
585
702
  });
586
703
 
@@ -588,7 +705,8 @@ describe('plugin-meetings', () => {
588
705
  const oldLocus = {controls: {breakout: {sessionType: 'BREAKOUT'}}};
589
706
  const newLocus = {controls: {breakout: {sessionType: 'MAIN'}}};
590
707
  assert.deepEqual(controlsUtils.getSessionSwitchStatus(oldLocus, newLocus), {
591
- isReturnToMain: true, isJoinToBreakout: false
708
+ isReturnToMain: true,
709
+ isJoinToBreakout: false,
592
710
  });
593
711
  });
594
712
 
@@ -596,55 +714,52 @@ describe('plugin-meetings', () => {
596
714
  const oldLocus = {controls: {breakout: {sessionType: 'MAIN'}}};
597
715
  const newLocus = {controls: {breakout: {sessionType: 'BREAKOUT'}}};
598
716
  assert.deepEqual(controlsUtils.getSessionSwitchStatus(oldLocus, newLocus), {
599
- isReturnToMain: false, isJoinToBreakout: true
717
+ isReturnToMain: false,
718
+ isJoinToBreakout: true,
600
719
  });
601
720
  });
602
721
 
603
722
  it('if needUseCache conditions are met, return isJoinToBreakout as true', () => {
604
723
  const oldLocus = {
605
- self: { isCreator: true },
606
- controls: { breakout: { sessionType: BREAKOUTS.SESSION_TYPES.MAIN} },
724
+ self: {isCreator: true},
725
+ controls: {breakout: {sessionType: BREAKOUTS.SESSION_TYPES.MAIN}},
607
726
  };
608
727
 
609
728
  const newLocus = {
610
- participants: [
611
- { isCreator: true, state: MEETING_STATE.STATES.JOINED },
612
- ],
729
+ participants: [{isCreator: true, state: MEETING_STATE.STATES.JOINED}],
613
730
  controls: {
614
731
  breakout: {
615
732
  sessionType: BREAKOUTS.SESSION_TYPES.MAIN,
616
- groups: [{ id: 'group1' }]
733
+ groups: [{id: 'group1'}],
617
734
  },
618
735
  },
619
736
  };
620
737
 
621
738
  assert.deepEqual(controlsUtils.getSessionSwitchStatus(oldLocus, newLocus), {
622
739
  isReturnToMain: true,
623
- isJoinToBreakout: false
740
+ isJoinToBreakout: false,
624
741
  });
625
742
  });
626
743
 
627
744
  it('if needUseCache conditions are not met, return newLocus and isReturnToMain as false', () => {
628
745
  const oldLocus = {
629
- self: { isCreator: false },
630
- controls: { breakout: { sessionType: BREAKOUTS.SESSION_TYPES.BREAKOUT} },
746
+ self: {isCreator: false},
747
+ controls: {breakout: {sessionType: BREAKOUTS.SESSION_TYPES.BREAKOUT}},
631
748
  };
632
749
 
633
750
  const newLocus = {
634
- participants: [
635
- { isCreator: true, state: MEETING_STATE.STATES.JOINED },
636
- ],
751
+ participants: [{isCreator: true, state: MEETING_STATE.STATES.JOINED}],
637
752
  controls: {
638
753
  breakout: {
639
754
  sessionType: BREAKOUTS.SESSION_TYPES.BREAKOUT,
640
- groups: []
755
+ groups: [],
641
756
  },
642
757
  },
643
758
  };
644
759
 
645
760
  assert.deepEqual(controlsUtils.getSessionSwitchStatus(oldLocus, newLocus), {
646
761
  isReturnToMain: false,
647
- isJoinToBreakout: false
762
+ isJoinToBreakout: false,
648
763
  });
649
764
  });
650
765
  });
@@ -652,7 +767,7 @@ describe('plugin-meetings', () => {
652
767
  describe('#isMainSessionDTO', () => {
653
768
  it('return false is sessionType is BREAKOUT', () => {
654
769
  const locus = {
655
- controls: {breakout: {sessionType: 'BREAKOUT'}}
770
+ controls: {breakout: {sessionType: 'BREAKOUT'}},
656
771
  };
657
772
 
658
773
  assert.equal(controlsUtils.isMainSessionDTO(locus), false);
@@ -660,7 +775,7 @@ describe('plugin-meetings', () => {
660
775
 
661
776
  it('return true is sessionType is not BREAKOUT', () => {
662
777
  const locus = {
663
- controls: {breakout: {sessionType: 'MAIN'}}
778
+ controls: {breakout: {sessionType: 'MAIN'}},
664
779
  };
665
780
 
666
781
  assert.equal(controlsUtils.isMainSessionDTO(locus), true);
@@ -1331,6 +1331,8 @@ describe('plugin-meetings', () => {
1331
1331
  state: RECORDING_STATE.IDLE,
1332
1332
  modifiedBy: 'George Kittle',
1333
1333
  lastModified: 'TODAY',
1334
+ modifiedByServiceAppName: undefined,
1335
+ modifiedByServiceAppId: undefined,
1334
1336
  }
1335
1337
  );
1336
1338
  });
@@ -1365,6 +1367,8 @@ describe('plugin-meetings', () => {
1365
1367
  state: RECORDING_STATE.RECORDING,
1366
1368
  modifiedBy: 'George Kittle',
1367
1369
  lastModified: 'TODAY',
1370
+ modifiedByServiceAppName: undefined,
1371
+ modifiedByServiceAppId: undefined,
1368
1372
  }
1369
1373
  );
1370
1374
  });
@@ -1400,6 +1404,8 @@ describe('plugin-meetings', () => {
1400
1404
  state: RECORDING_STATE.PAUSED,
1401
1405
  modifiedBy: 'George Kittle',
1402
1406
  lastModified: 'TODAY',
1407
+ modifiedByServiceAppName: undefined,
1408
+ modifiedByServiceAppId: undefined,
1403
1409
  }
1404
1410
  );
1405
1411
  });
@@ -1436,6 +1442,8 @@ describe('plugin-meetings', () => {
1436
1442
  state: RECORDING_STATE.RESUMED,
1437
1443
  modifiedBy: 'George Kittle',
1438
1444
  lastModified: 'TODAY',
1445
+ modifiedByServiceAppName: undefined,
1446
+ modifiedByServiceAppId: undefined,
1439
1447
  }
1440
1448
  );
1441
1449
  });
@@ -1471,6 +1479,44 @@ describe('plugin-meetings', () => {
1471
1479
  state: RECORDING_STATE.IDLE,
1472
1480
  modifiedBy: 'George Kittle',
1473
1481
  lastModified: 'TODAY',
1482
+ modifiedByServiceAppName: undefined,
1483
+ modifiedByServiceAppId: undefined,
1484
+ }
1485
+ );
1486
+ });
1487
+
1488
+ it('should include service app fields in the recording event when present', () => {
1489
+ locusInfo.controls = {
1490
+ record: {
1491
+ recording: false,
1492
+ paused: false,
1493
+ meta: {
1494
+ lastModified: 'TODAY',
1495
+ modifiedBy: 'George Kittle',
1496
+ },
1497
+ },
1498
+ shareControl: {},
1499
+ transcribe: {},
1500
+ };
1501
+ newControls.record.recording = true;
1502
+ newControls.record.meta.modifiedByServiceAppName = 'My Bot';
1503
+ newControls.record.meta.modifiedByServiceAppId = 'app-id-123';
1504
+ locusInfo.emitScoped = sinon.stub();
1505
+ locusInfo.updateControls(newControls);
1506
+
1507
+ assert.calledWith(
1508
+ locusInfo.emitScoped,
1509
+ {
1510
+ file: 'locus-info',
1511
+ function: 'updateControls',
1512
+ },
1513
+ LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
1514
+ {
1515
+ state: RECORDING_STATE.RECORDING,
1516
+ modifiedBy: 'George Kittle',
1517
+ lastModified: 'TODAY',
1518
+ modifiedByServiceAppName: 'My Bot',
1519
+ modifiedByServiceAppId: 'app-id-123',
1474
1520
  }
1475
1521
  );
1476
1522
  });
@@ -114,6 +114,7 @@ describe('plugin-meetings', () => {
114
114
  canDisablePollingQA: null,
115
115
  canAttendeeRequestAiAssistantEnabled: null,
116
116
  isAttendeeRequestAiAssistantDeclinedAll: null,
117
+ isAnonymizeDisplayNamesEnabled: null,
117
118
 
118
119
  ...expected,
119
120
  };
@@ -234,6 +235,7 @@ describe('plugin-meetings', () => {
234
235
  'canDisablePollingQA',
235
236
  'canAttendeeRequestAiAssistantEnabled',
236
237
  'isAttendeeRequestAiAssistantDeclinedAll',
238
+ 'isAnonymizeDisplayNamesEnabled',
237
239
  ].forEach((key) => {
238
240
  it(`get and set for ${key} work as expected`, () => {
239
241
  const inMeetingActions = new InMeetingActions();
@@ -35,6 +35,7 @@ import {
35
35
  OFFLINE,
36
36
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
37
37
  LOCUS_LLM_EVENT,
38
+ RECORDING_STATE,
38
39
  } from '@webex/plugin-meetings/src/constants';
39
40
  import {
40
41
  ConnectionState,
@@ -11390,6 +11391,92 @@ describe('plugin-meetings', () => {
11390
11391
  );
11391
11392
  });
11392
11393
 
11394
+ const recordingTestCases = [
11395
+ {
11396
+ description: 'triggers MEETING_STARTED_RECORDING when state is RECORDING',
11397
+ state: RECORDING_STATE.RECORDING,
11398
+ expectedEvent: EVENT_TRIGGERS.MEETING_STARTED_RECORDING,
11399
+ expectedRecordingState: RECORDING_STATE.RECORDING,
11400
+ },
11401
+ {
11402
+ description: 'triggers MEETING_STOPPED_RECORDING when state is IDLE',
11403
+ state: RECORDING_STATE.IDLE,
11404
+ expectedEvent: EVENT_TRIGGERS.MEETING_STOPPED_RECORDING,
11405
+ expectedRecordingState: RECORDING_STATE.IDLE,
11406
+ },
11407
+ {
11408
+ description: 'triggers MEETING_PAUSED_RECORDING when state is PAUSED',
11409
+ state: RECORDING_STATE.PAUSED,
11410
+ expectedEvent: EVENT_TRIGGERS.MEETING_PAUSED_RECORDING,
11411
+ expectedRecordingState: RECORDING_STATE.PAUSED,
11412
+ },
11413
+ {
11414
+ description:
11415
+ 'triggers MEETING_RESUMED_RECORDING and sets state to RECORDING when state is RESUMED',
11416
+ state: RECORDING_STATE.RESUMED,
11417
+ expectedEvent: EVENT_TRIGGERS.MEETING_RESUMED_RECORDING,
11418
+ expectedRecordingState: RECORDING_STATE.RECORDING,
11419
+ },
11420
+ ];
11421
+
11422
+ recordingTestCases.forEach(({description, state, expectedEvent, expectedRecordingState}) => {
11423
+ it(`listens to CONTROLS_RECORDING_UPDATED - ${description}`, async () => {
11424
+ const modifiedBy = 'user-id-123';
11425
+ const lastModified = '2026-01-01T00:00:00Z';
11426
+
11427
+ await meeting.locusInfo.emitScoped(
11428
+ {function: 'test', file: 'test'},
11429
+ LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
11430
+ {state, modifiedBy, lastModified, modifiedByServiceAppName: undefined, modifiedByServiceAppId: undefined}
11431
+ );
11432
+
11433
+ assert.deepEqual(meeting.recording, {
11434
+ state: expectedRecordingState,
11435
+ modifiedBy,
11436
+ lastModified,
11437
+ modifiedByServiceAppName: undefined,
11438
+ modifiedByServiceAppId: undefined,
11439
+ });
11440
+
11441
+ assert.calledWith(
11442
+ TriggerProxy.trigger,
11443
+ meeting,
11444
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
11445
+ expectedEvent,
11446
+ meeting.recording
11447
+ );
11448
+ });
11449
+ });
11450
+
11451
+ it('listens to CONTROLS_RECORDING_UPDATED and includes modifiedByServiceAppName and modifiedByServiceAppId when present', async () => {
11452
+ const modifiedBy = 'user-id-123';
11453
+ const lastModified = '2026-01-01T00:00:00Z';
11454
+ const modifiedByServiceAppName = 'My Bot';
11455
+ const modifiedByServiceAppId = 'app-id-123';
11456
+
11457
+ await meeting.locusInfo.emitScoped(
11458
+ {function: 'test', file: 'test'},
11459
+ LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
11460
+ {state: RECORDING_STATE.RECORDING, modifiedBy, lastModified, modifiedByServiceAppName, modifiedByServiceAppId}
11461
+ );
11462
+
11463
+ assert.deepEqual(meeting.recording, {
11464
+ state: RECORDING_STATE.RECORDING,
11465
+ modifiedBy,
11466
+ lastModified,
11467
+ modifiedByServiceAppName,
11468
+ modifiedByServiceAppId,
11469
+ });
11470
+
11471
+ assert.calledWith(
11472
+ TriggerProxy.trigger,
11473
+ meeting,
11474
+ {file: 'meeting/index', function: 'setupLocusControlsListener'},
11475
+ EVENT_TRIGGERS.MEETING_STARTED_RECORDING,
11476
+ meeting.recording
11477
+ );
11478
+ });
11479
+
11393
11480
  it('listens to the locus interpretation update event', () => {
11394
11481
  const interpretation = {
11395
11482
  siLanguages: [{languageCode: 20, languageName: 'en'}],
@@ -12463,6 +12550,7 @@ describe('plugin-meetings', () => {
12463
12550
  let showAutoEndMeetingWarningSpy;
12464
12551
  let canAttendeeRequestAiAssistantEnabledSpy;
12465
12552
  let attendeeRequestAiAssistantDeclinedAllSpy;
12553
+ let isAnonymizeDisplayNamesEnabledSpy;
12466
12554
  // Due to import tree issues, hasHints must be stubed within the scope of the `it`.
12467
12555
 
12468
12556
  beforeEach(() => {
@@ -12511,6 +12599,10 @@ describe('plugin-meetings', () => {
12511
12599
  MeetingUtil,
12512
12600
  'attendeeRequestAiAssistantDeclinedAll'
12513
12601
  );
12602
+ isAnonymizeDisplayNamesEnabledSpy = sinon.spy(
12603
+ MeetingUtil,
12604
+ 'isAnonymizeDisplayNamesEnabled'
12605
+ );
12514
12606
  });
12515
12607
 
12516
12608
  afterEach(() => {
@@ -12519,6 +12611,7 @@ describe('plugin-meetings', () => {
12519
12611
  showAutoEndMeetingWarningSpy.restore();
12520
12612
  canAttendeeRequestAiAssistantEnabledSpy.restore();
12521
12613
  attendeeRequestAiAssistantDeclinedAllSpy.restore();
12614
+ isAnonymizeDisplayNamesEnabledSpy.restore();
12522
12615
  });
12523
12616
 
12524
12617
  forEach(
@@ -13076,6 +13169,7 @@ describe('plugin-meetings', () => {
13076
13169
  meeting.roles
13077
13170
  );
13078
13171
  assert.calledWith(attendeeRequestAiAssistantDeclinedAllSpy, userDisplayHints);
13172
+ assert.calledWith(isAnonymizeDisplayNamesEnabledSpy, userDisplayHints);
13079
13173
 
13080
13174
  assert.calledWith(ControlsOptionsUtil.hasHints, {
13081
13175
  requiredHints: [DISPLAY_HINTS.MUTE_ALL],
@@ -1175,6 +1175,10 @@ describe('plugin-meetings', () => {
1175
1175
  {functionName: 'canSelectSpokenLanguages', displayHint: 'DISPLAY_NON_ENGLISH_ASR'},
1176
1176
  {functionName: 'waitingForOthersToJoin', displayHint: 'WAITING_FOR_OTHERS'},
1177
1177
  {functionName: 'showAutoEndMeetingWarning', displayHint: 'SHOW_AUTO_END_MEETING_WARNING'},
1178
+ {
1179
+ functionName: 'isAnonymizeDisplayNamesEnabled',
1180
+ displayHint: 'ANONYMOUS_DISPLAY_NAMES_ENABLED',
1181
+ },
1178
1182
  ].forEach(({functionName, displayHint}) => {
1179
1183
  describe(functionName, () => {
1180
1184
  it('works as expected', () => {