@webex/plugin-meetings 3.12.0-next.2 → 3.12.0-next.21

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 (67) hide show
  1. package/dist/aiEnableRequest/index.js +1 -1
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/controls-options-manager/constants.js +11 -1
  5. package/dist/controls-options-manager/constants.js.map +1 -1
  6. package/dist/controls-options-manager/index.js +23 -21
  7. package/dist/controls-options-manager/index.js.map +1 -1
  8. package/dist/controls-options-manager/util.js +91 -0
  9. package/dist/controls-options-manager/util.js.map +1 -1
  10. package/dist/hashTree/constants.js +10 -1
  11. package/dist/hashTree/constants.js.map +1 -1
  12. package/dist/hashTree/hashTreeParser.js +56 -31
  13. package/dist/hashTree/hashTreeParser.js.map +1 -1
  14. package/dist/hashTree/utils.js +22 -0
  15. package/dist/hashTree/utils.js.map +1 -1
  16. package/dist/interpretation/index.js +1 -1
  17. package/dist/interpretation/siLanguage.js +1 -1
  18. package/dist/locus-info/index.js +51 -23
  19. package/dist/locus-info/index.js.map +1 -1
  20. package/dist/meeting/index.js +372 -292
  21. package/dist/meeting/index.js.map +1 -1
  22. package/dist/meeting/util.js +1 -0
  23. package/dist/meeting/util.js.map +1 -1
  24. package/dist/meetings/index.js +8 -9
  25. package/dist/meetings/index.js.map +1 -1
  26. package/dist/meetings/util.js +21 -2
  27. package/dist/meetings/util.js.map +1 -1
  28. package/dist/metrics/constants.js +5 -1
  29. package/dist/metrics/constants.js.map +1 -1
  30. package/dist/multistream/sendSlotManager.js +116 -2
  31. package/dist/multistream/sendSlotManager.js.map +1 -1
  32. package/dist/types/controls-options-manager/constants.d.ts +6 -1
  33. package/dist/types/hashTree/constants.d.ts +1 -0
  34. package/dist/types/hashTree/hashTreeParser.d.ts +12 -2
  35. package/dist/types/hashTree/utils.d.ts +11 -0
  36. package/dist/types/locus-info/index.d.ts +9 -5
  37. package/dist/types/meeting/index.d.ts +11 -0
  38. package/dist/types/metrics/constants.d.ts +4 -0
  39. package/dist/types/multistream/sendSlotManager.d.ts +23 -1
  40. package/dist/webinar/index.js +301 -226
  41. package/dist/webinar/index.js.map +1 -1
  42. package/package.json +16 -16
  43. package/src/controls-options-manager/constants.ts +14 -1
  44. package/src/controls-options-manager/index.ts +26 -19
  45. package/src/controls-options-manager/util.ts +81 -1
  46. package/src/hashTree/constants.ts +9 -0
  47. package/src/hashTree/hashTreeParser.ts +60 -36
  48. package/src/hashTree/utils.ts +17 -0
  49. package/src/locus-info/index.ts +56 -30
  50. package/src/meeting/index.ts +98 -11
  51. package/src/meeting/util.ts +1 -0
  52. package/src/meetings/index.ts +15 -16
  53. package/src/meetings/util.ts +26 -1
  54. package/src/metrics/constants.ts +5 -0
  55. package/src/multistream/sendSlotManager.ts +97 -3
  56. package/src/webinar/index.ts +75 -1
  57. package/test/unit/spec/controls-options-manager/index.js +114 -6
  58. package/test/unit/spec/controls-options-manager/util.js +165 -0
  59. package/test/unit/spec/hashTree/hashTreeParser.ts +441 -30
  60. package/test/unit/spec/hashTree/utils.ts +88 -1
  61. package/test/unit/spec/locus-info/index.js +75 -27
  62. package/test/unit/spec/meeting/index.js +54 -36
  63. package/test/unit/spec/meeting/utils.js +4 -0
  64. package/test/unit/spec/meetings/index.js +36 -3
  65. package/test/unit/spec/meetings/utils.js +108 -0
  66. package/test/unit/spec/multistream/sendSlotManager.ts +135 -36
  67. package/test/unit/spec/webinar/index.ts +60 -0
@@ -1,5 +1,10 @@
1
1
  import {HashTreeObject, ObjectType} from '../../../../src/hashTree/types';
2
- import {deleteNestedObjectsWithHtMeta, isSelf} from '../../../../src/hashTree/utils';
2
+ import {
3
+ deleteNestedObjectsWithHtMeta,
4
+ isSelf,
5
+ sortByInitPriority,
6
+ } from '../../../../src/hashTree/utils';
7
+ import {DataSetNames, DATA_SET_INIT_PRIORITY} from '../../../../src/hashTree/constants';
3
8
 
4
9
  import {assert} from '@webex/test-helper-chai';
5
10
 
@@ -137,4 +142,86 @@ describe('Hash Tree Utils', () => {
137
142
  assert.isFalse(isSelf(participantObject));
138
143
  });
139
144
  });
145
+
146
+ describe('#sortByInitPriority', () => {
147
+ [
148
+ {
149
+ description: 'places "main" and "self" first when both appear',
150
+ input: ['atd-active', 'main', 'atd-unmuted', 'self'],
151
+ expected: ['main', 'self', 'atd-active', 'atd-unmuted'],
152
+ },
153
+ {
154
+ description: 'preserves original order of non-priority items',
155
+ input: ['atd-unmuted', 'atd-active', 'self'],
156
+ expected: ['self', 'atd-unmuted', 'atd-active'],
157
+ },
158
+ {
159
+ description: 'returns items unchanged when no priority items present',
160
+ input: ['atd-active', 'atd-unmuted'],
161
+ expected: ['atd-active', 'atd-unmuted'],
162
+ },
163
+ {
164
+ description: 'reorders when only priority items present',
165
+ input: ['self', 'main'],
166
+ expected: ['main', 'self'],
167
+ },
168
+ {
169
+ description: 'handles empty list',
170
+ input: [],
171
+ expected: [],
172
+ },
173
+ {
174
+ description: 'handles only some priority items present',
175
+ input: ['atd-active', 'main'],
176
+ expected: ['main', 'atd-active'],
177
+ },
178
+ {
179
+ description: 'handles single non-priority item',
180
+ input: ['atd-active'],
181
+ expected: ['atd-active'],
182
+ },
183
+ {
184
+ description: 'handles single priority item',
185
+ input: ['self'],
186
+ expected: ['self'],
187
+ },
188
+ ].forEach(({description, input, expected}) => {
189
+ it(description, () => {
190
+ const items = input.map((name) => ({name}));
191
+
192
+ const result = sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
193
+
194
+ assert.deepEqual(
195
+ result.map((i) => i.name),
196
+ expected
197
+ );
198
+ });
199
+ });
200
+
201
+ it('should not mutate the original array', () => {
202
+ const items = [{name: DataSetNames.ATD_ACTIVE}, {name: DataSetNames.SELF}];
203
+ const originalOrder = items.map((i) => i.name);
204
+
205
+ sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
206
+
207
+ assert.deepEqual(
208
+ items.map((i) => i.name),
209
+ originalOrder
210
+ );
211
+ });
212
+
213
+ it('should preserve extra properties on items', () => {
214
+ const items = [
215
+ {name: DataSetNames.ATD_ACTIVE, url: 'url1'},
216
+ {name: DataSetNames.SELF, url: 'url2'},
217
+ ];
218
+
219
+ const result = sortByInitPriority(items, DATA_SET_INIT_PRIORITY);
220
+
221
+ assert.deepEqual(result, [
222
+ {name: DataSetNames.SELF, url: 'url2'},
223
+ {name: DataSetNames.ATD_ACTIVE, url: 'url1'},
224
+ ]);
225
+ });
226
+ });
140
227
  });
@@ -413,7 +413,7 @@ describe('plugin-meetings', () => {
413
413
  };
414
414
 
415
415
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
416
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
416
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
417
417
  updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
418
418
  });
419
419
 
@@ -440,7 +440,7 @@ describe('plugin-meetings', () => {
440
440
  locusInfo.info.isWebinar = true;
441
441
 
442
442
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
443
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
443
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
444
444
  updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
445
445
  });
446
446
 
@@ -473,7 +473,7 @@ describe('plugin-meetings', () => {
473
473
  locusInfo.info.isWebinar = true;
474
474
 
475
475
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
476
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
476
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
477
477
  updatedObjects: [{htMeta: {elementId: {type: 'self'}}, data: newSelf}],
478
478
  });
479
479
 
@@ -501,7 +501,7 @@ describe('plugin-meetings', () => {
501
501
  };
502
502
 
503
503
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
504
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
504
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
505
505
  updatedObjects: [{htMeta: {elementId: {type: 'fullState'}}, data: newFullState}],
506
506
  });
507
507
 
@@ -519,7 +519,7 @@ describe('plugin-meetings', () => {
519
519
  };
520
520
 
521
521
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
522
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
522
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
523
523
  updatedObjects: [{htMeta: {elementId: {type: 'info'}}, data: newInfo}],
524
524
  });
525
525
 
@@ -537,7 +537,7 @@ describe('plugin-meetings', () => {
537
537
  };
538
538
 
539
539
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
540
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
540
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
541
541
  updatedObjects: [{htMeta: {elementId: {type: 'links'}}, data: newLinks}],
542
542
  });
543
543
 
@@ -557,7 +557,7 @@ describe('plugin-meetings', () => {
557
557
  };
558
558
 
559
559
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
560
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
560
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
561
561
  updatedObjects: [{htMeta: newLocusHtMeta, data: newLocus}],
562
562
  });
563
563
 
@@ -590,7 +590,7 @@ describe('plugin-meetings', () => {
590
590
  };
591
591
 
592
592
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
593
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
593
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
594
594
  updatedObjects: [
595
595
  {
596
596
  htMeta: newLocusHtMeta,
@@ -637,7 +637,7 @@ describe('plugin-meetings', () => {
637
637
  };
638
638
 
639
639
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
640
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
640
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
641
641
  updatedObjects: [
642
642
  // first, a removal of LOCUS object
643
643
  {htMeta: {elementId: {type: 'locus'}}, data: null},
@@ -671,7 +671,7 @@ describe('plugin-meetings', () => {
671
671
  const newLocusHtMeta = {elementId: {type: 'locus', version: 99}};
672
672
 
673
673
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
674
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
674
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
675
675
  updatedObjects: [
676
676
  // first, an update
677
677
  {htMeta: newLocusHtMeta, data: newLocus},
@@ -700,7 +700,7 @@ describe('plugin-meetings', () => {
700
700
  };
701
701
 
702
702
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
703
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
703
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
704
704
  updatedObjects: [
705
705
  // first, an update
706
706
  {htMeta: {elementId: {type: 'locus'}}, data: newLocus1},
@@ -730,7 +730,7 @@ describe('plugin-meetings', () => {
730
730
  };
731
731
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
732
732
  // with 1 participant added, 1 updated, and 1 removed
733
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
733
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
734
734
  updatedObjects: [
735
735
  {htMeta: {elementId: {type: 'participant', id: 'fake-ht-participant-1'}}, data: null},
736
736
  {
@@ -774,7 +774,7 @@ describe('plugin-meetings', () => {
774
774
  };
775
775
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
776
776
  // with 1 participant added, 1 updated, and 1 removed
777
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
777
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
778
778
  updatedObjects: [
779
779
  {htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-1'}}, data: null},
780
780
  {
@@ -807,7 +807,7 @@ describe('plugin-meetings', () => {
807
807
  };
808
808
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
809
809
  // with 1 embedded app added, 1 updated, and 1 removed
810
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
810
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
811
811
  updatedObjects: [
812
812
  {htMeta: {elementId: {type: 'embeddedapp', id: 'fake-ht-embeddedApp-1'}}, data: null},
813
813
  {
@@ -844,7 +844,7 @@ describe('plugin-meetings', () => {
844
844
  };
845
845
 
846
846
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
847
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
847
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
848
848
  updatedObjects: [
849
849
  {
850
850
  htMeta: {elementId: {type: 'mediashare', id: 'fake-ht-mediaShare-2'}},
@@ -883,7 +883,7 @@ describe('plugin-meetings', () => {
883
883
  };
884
884
 
885
885
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
886
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
886
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
887
887
  updatedObjects: [
888
888
  {
889
889
  htMeta: {elementId: {type: 'controlentry', id: 'control-1'}},
@@ -911,7 +911,7 @@ describe('plugin-meetings', () => {
911
911
 
912
912
  it('should process locus update correctly when CONTROL object is received with no data', () => {
913
913
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
914
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
914
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
915
915
  updatedObjects: [
916
916
  {
917
917
  htMeta: {elementId: {type: 'controlentry', id: 'some-control-id'}},
@@ -935,7 +935,7 @@ describe('plugin-meetings', () => {
935
935
  const destroyStub = sinon.stub(locusInfo.webex.meetings, 'destroy');
936
936
 
937
937
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
938
- locusInfoUpdateCallback(MEETING_ENDED);
938
+ locusInfoUpdateCallback({updateType: MEETING_ENDED});
939
939
 
940
940
  assert.calledOnceWithExactly(collectionGetStub, locusInfo.meetingId);
941
941
  assert.calledOnceWithExactly(
@@ -953,7 +953,7 @@ describe('plugin-meetings', () => {
953
953
  const destroyStub = sinon.stub(locusInfo.webex.meetings, 'destroy');
954
954
 
955
955
  // simulate an update from the HashTreeParser (normally this would be triggered by incoming locus messages)
956
- locusInfoUpdateCallback(MEETING_ENDED);
956
+ locusInfoUpdateCallback({updateType: MEETING_ENDED});
957
957
 
958
958
  assert.calledOnceWithExactly(collectionGetStub, locusInfo.meetingId);
959
959
  assert.notCalled(destroyStub);
@@ -963,7 +963,7 @@ describe('plugin-meetings', () => {
963
963
  const createdHashTreeParser = locusInfo.hashTreeParsers.get('fake-locus-url');
964
964
  createdHashTreeParser.initializedFromHashTree = false;
965
965
 
966
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
966
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
967
967
  updatedObjects: [
968
968
  {
969
969
  htMeta: {elementId: {type: 'self'}},
@@ -978,7 +978,7 @@ describe('plugin-meetings', () => {
978
978
  });
979
979
 
980
980
  it('should set forceReplaceMembers to false on subsequent updates (initializedFromHashTree is true)', () => {
981
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
981
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
982
982
  updatedObjects: [
983
983
  {
984
984
  htMeta: {elementId: {type: 'self'}},
@@ -994,7 +994,7 @@ describe('plugin-meetings', () => {
994
994
  it('should copy participant data to self when participant matches self identity and state is LEFT with reason MOVED', () => {
995
995
  locusInfo.self = {id: 'fake-self', identity: 'user-123'};
996
996
 
997
- locusInfoUpdateCallback(OBJECTS_UPDATED, {
997
+ locusInfoUpdateCallback({updateType: OBJECTS_UPDATED,
998
998
  updatedObjects: [
999
999
  {
1000
1000
  htMeta: {elementId: {type: 'participant', id: 99}},
@@ -2024,7 +2024,7 @@ describe('plugin-meetings', () => {
2024
2024
  function: 'updateSelf',
2025
2025
  },
2026
2026
  LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
2027
- {muted: true, unmuteAllowed: true}
2027
+ {muted: true, unmuteAllowed: true, modifiedBy: null}
2028
2028
  );
2029
2029
 
2030
2030
  // but sometimes "previous self" is defined, but without controls.audio.muted, so we test this here:
@@ -2039,7 +2039,7 @@ describe('plugin-meetings', () => {
2039
2039
  function: 'updateSelf',
2040
2040
  },
2041
2041
  LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
2042
- {muted: true, unmuteAllowed: true}
2042
+ {muted: true, unmuteAllowed: true, modifiedBy: null}
2043
2043
  );
2044
2044
  });
2045
2045
 
@@ -2098,7 +2098,7 @@ describe('plugin-meetings', () => {
2098
2098
  function: 'updateSelf',
2099
2099
  },
2100
2100
  LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
2101
- {muted: true, unmuteAllowed: true}
2101
+ {muted: true, unmuteAllowed: true, modifiedBy: null}
2102
2102
  );
2103
2103
  });
2104
2104
 
@@ -2237,7 +2237,7 @@ describe('plugin-meetings', () => {
2237
2237
  function: 'updateSelf',
2238
2238
  },
2239
2239
  LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
2240
- {muted: true, unmuteAllowed: false}
2240
+ {muted: true, unmuteAllowed: false, modifiedBy: null}
2241
2241
  );
2242
2242
 
2243
2243
  // now change only disallowUnmute
@@ -2255,7 +2255,28 @@ describe('plugin-meetings', () => {
2255
2255
  function: 'updateSelf',
2256
2256
  },
2257
2257
  LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
2258
- {muted: true, unmuteAllowed: true}
2258
+ {muted: true, unmuteAllowed: true, modifiedBy: null}
2259
+ );
2260
+ });
2261
+
2262
+ it('should include modifiedBy in payload when muted by host', () => {
2263
+ locusInfo.webex.internal.device.url = self.deviceUrl;
2264
+ locusInfo.updateSelf(self);
2265
+ const newSelf = cloneDeep(self);
2266
+ newSelf.controls.audio.muted = true;
2267
+ newSelf.controls.audio.meta = {modifiedBy: 'host-uuid-123'};
2268
+
2269
+ locusInfo.emitScoped = sinon.stub();
2270
+ locusInfo.updateSelf(newSelf);
2271
+
2272
+ assert.calledWith(
2273
+ locusInfo.emitScoped,
2274
+ {
2275
+ file: 'locus-info',
2276
+ function: 'updateSelf',
2277
+ },
2278
+ LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED,
2279
+ {muted: true, unmuteAllowed: true, modifiedBy: 'host-uuid-123'}
2259
2280
  );
2260
2281
  });
2261
2282
 
@@ -3161,6 +3182,7 @@ describe('plugin-meetings', () => {
3161
3182
  const createSelfElementWithReplaces = (replacedLocusUrl, replacedAt) => ({
3162
3183
  htMeta: {elementId: {type: 'Self'}},
3163
3184
  data: {
3185
+ deviceUrl,
3164
3186
  devices: [{url: deviceUrl, replaces: [{locusUrl: replacedLocusUrl, replacedAt}]}],
3165
3187
  },
3166
3188
  });
@@ -4413,6 +4435,31 @@ describe('plugin-meetings', () => {
4413
4435
  });
4414
4436
  });
4415
4437
 
4438
+ describe('#cleanUp', () => {
4439
+ it('calls cleanUp on all hash tree parsers and clears maps', () => {
4440
+ const parser1 = {cleanUp: sinon.stub()};
4441
+ const parser2 = {cleanUp: sinon.stub()};
4442
+
4443
+ locusInfo.hashTreeParsers.set('url1', {parser: parser1, initializedFromHashTree: true});
4444
+ locusInfo.hashTreeParsers.set('url2', {parser: parser2, initializedFromHashTree: true});
4445
+ locusInfo.hashTreeObjectId2ParticipantId.set(1, 'participant1');
4446
+
4447
+ locusInfo.cleanUp();
4448
+
4449
+ assert.calledOnce(parser1.cleanUp);
4450
+ assert.calledOnce(parser2.cleanUp);
4451
+ assert.equal(locusInfo.hashTreeParsers.size, 0);
4452
+ assert.equal(locusInfo.hashTreeObjectId2ParticipantId.size, 0);
4453
+ });
4454
+
4455
+ it('works when there are no hash tree parsers', () => {
4456
+ locusInfo.cleanUp();
4457
+
4458
+ assert.equal(locusInfo.hashTreeParsers.size, 0);
4459
+ assert.equal(locusInfo.hashTreeObjectId2ParticipantId.size, 0);
4460
+ });
4461
+ });
4462
+
4416
4463
  describe('#handleOneonOneEvent', () => {
4417
4464
  beforeEach(() => {
4418
4465
  locusInfo.emitScoped = sinon.stub();
@@ -5146,6 +5193,7 @@ describe('plugin-meetings', () => {
5146
5193
  return {
5147
5194
  htMeta: {elementId: {type: 'Self'}},
5148
5195
  data: {
5196
+ deviceUrl,
5149
5197
  devices,
5150
5198
  },
5151
5199
  };
@@ -38,6 +38,7 @@ import {
38
38
  import {
39
39
  ConnectionState,
40
40
  MediaConnectionEventNames,
41
+ MediaCodecMimeType,
41
42
  StatsAnalyzerEventNames,
42
43
  StatsMonitorEventNames,
43
44
  Errors,
@@ -1981,11 +1982,12 @@ describe('plugin-meetings', () => {
1981
1982
  describe('#handleLLMOnline', () => {
1982
1983
  beforeEach(() => {
1983
1984
  webex.internal.llm.off = sinon.stub();
1985
+ webex.internal.voicea.getIsCaptionBoxOn = sinon.stub().returns(false);
1986
+ webex.internal.voicea.updateSubchannelSubscriptions = sinon.stub();
1984
1987
  });
1985
1988
 
1986
- it('turns off llm online, emits transcription connected events', () => {
1989
+ it('emits transcription connected events', () => {
1987
1990
  meeting.handleLLMOnline();
1988
- assert.calledOnceWithExactly(webex.internal.llm.off, 'online', meeting.handleLLMOnline);
1989
1991
  assert.calledWith(
1990
1992
  TriggerProxy.trigger,
1991
1993
  sinon.match.instanceOf(Meeting),
@@ -1996,6 +1998,24 @@ describe('plugin-meetings', () => {
1996
1998
  EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED
1997
1999
  );
1998
2000
  });
2001
+
2002
+ it('restores transcription subscription when caption intent is enabled', () => {
2003
+ webex.internal.voicea.getIsCaptionBoxOn.returns(true);
2004
+
2005
+ meeting.handleLLMOnline();
2006
+
2007
+ assert.calledOnceWithExactly(webex.internal.voicea.updateSubchannelSubscriptions, {
2008
+ subscribe: ['transcription'],
2009
+ });
2010
+ });
2011
+
2012
+ it('does not restore transcription subscription when caption intent is disabled', () => {
2013
+ webex.internal.voicea.getIsCaptionBoxOn.returns(false);
2014
+
2015
+ meeting.handleLLMOnline();
2016
+
2017
+ assert.notCalled(webex.internal.voicea.updateSubchannelSubscriptions);
2018
+ });
1999
2019
  });
2000
2020
 
2001
2021
  describe('#join', () => {
@@ -2015,6 +2035,7 @@ describe('plugin-meetings', () => {
2015
2035
  it('should have #join', () => {
2016
2036
  assert.exists(meeting.join);
2017
2037
  });
2038
+
2018
2039
  beforeEach(() => {
2019
2040
  setCorrelationIdSpy = sinon.spy(meeting, 'setCorrelationId');
2020
2041
  meeting.setLocus = sinon.stub().returns(true);
@@ -2168,7 +2189,6 @@ describe('plugin-meetings', () => {
2168
2189
  await meeting.join().catch(() => {
2169
2190
  assert.calledOnce(MeetingUtil.joinMeeting);
2170
2191
 
2171
- // Assert that client.locus.join.response error event is not sent from this function, it is now emitted from MeetingUtil.joinMeeting
2172
2192
  assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
2173
2193
  assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
2174
2194
  name: 'client.call.initiated',
@@ -2200,6 +2220,7 @@ describe('plugin-meetings', () => {
2200
2220
  });
2201
2221
  });
2202
2222
  });
2223
+
2203
2224
  describe('lmm, transcription & permissionTokenRefresh decoupling', () => {
2204
2225
  beforeEach(() => {
2205
2226
  sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.resolve(joinMeetingResult));
@@ -2270,7 +2291,6 @@ describe('plugin-meetings', () => {
2270
2291
  const locusInfoParseStub = sinon.stub(meeting.locusInfo, 'parse');
2271
2292
  sinon.stub(meeting, 'isJoined').returns(true);
2272
2293
 
2273
- // Set up llm.on stub to capture the registered listener when updateLLMConnection is called
2274
2294
  let locusLLMEventListener;
2275
2295
  meeting.webex.internal.llm.on = sinon.stub().callsFake((eventName, callback) => {
2276
2296
  if (eventName === 'event:locus.state_message') {
@@ -2279,16 +2299,12 @@ describe('plugin-meetings', () => {
2279
2299
  });
2280
2300
  meeting.webex.internal.llm.off = sinon.stub();
2281
2301
 
2282
- // we need the real meeting.updateLLMConnection not the mock
2283
2302
  meeting.updateLLMConnection.restore();
2284
2303
 
2285
- // Call updateLLMConnection to register the listener
2286
2304
  await meeting.updateLLMConnection();
2287
2305
 
2288
- // Verify the listener was registered and we captured it
2289
2306
  assert.isDefined(locusLLMEventListener, 'LLM event listener should be registered');
2290
2307
 
2291
- // Now trigger the event
2292
2308
  const eventData = {
2293
2309
  eventType: 'locus.state_message',
2294
2310
  stateElementsMessage: {
@@ -2308,13 +2324,10 @@ describe('plugin-meetings', () => {
2308
2324
  sinon.stub(meeting.webex.internal.llm, 'hasEverConnected').value(true);
2309
2325
  sinon.stub(meeting.webex.internal.llm, 'registerAndConnect').resolves({});
2310
2326
 
2311
- // Restore the real updateLLMConnection
2312
2327
  meeting.updateLLMConnection.restore();
2313
2328
 
2314
- // Call updateLLMConnection to start the timer
2315
2329
  await meeting.updateLLMConnection();
2316
2330
 
2317
- // Fast forward time by 3 minutes
2318
2331
  fakeClock.tick(3 * 60 * 1000);
2319
2332
 
2320
2333
  assert.calledWith(
@@ -2339,18 +2352,14 @@ describe('plugin-meetings', () => {
2339
2352
  .stub(meeting.webex.internal.llm, 'getDatachannelUrl')
2340
2353
  .returns('https://datachannel1.example.com');
2341
2354
 
2342
- // Restore the real updateLLMConnection
2343
2355
  meeting.updateLLMConnection.restore();
2344
2356
 
2345
- // First, connect LLM and start the timer
2346
2357
  isJoinedStub.returns(true);
2347
2358
  meeting.webex.internal.llm.isConnected.returns(false);
2348
2359
  await meeting.updateLLMConnection();
2349
2360
 
2350
- // Verify timer was started
2351
2361
  assert.exists(meeting.llmHealthCheckTimer);
2352
2362
 
2353
- // Now simulate that we're no longer joined
2354
2363
  isJoinedStub.returns(false);
2355
2364
  meeting.webex.internal.llm.isConnected.returns(true);
2356
2365
 
@@ -2358,10 +2367,8 @@ describe('plugin-meetings', () => {
2358
2367
 
2359
2368
  assert.calledOnce(meeting.webex.internal.llm.disconnectLLM);
2360
2369
 
2361
- // Verify the timer was cleared (should be undefined)
2362
2370
  assert.isUndefined(meeting.llmHealthCheckTimer);
2363
2371
 
2364
- // Fast forward time to ensure no metric is sent
2365
2372
  Metrics.sendBehavioralMetric.resetHistory();
2366
2373
  fakeClock.tick(3 * 60 * 1000);
2367
2374
 
@@ -2396,7 +2403,6 @@ describe('plugin-meetings', () => {
2396
2403
  .stub()
2397
2404
  .rejects(new CaptchaError('bad captcha'));
2398
2405
  const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
2399
- const joinMeetingOptionsSpy = sinon.spy(MeetingUtil, 'joinMeetingOptions');
2400
2406
 
2401
2407
  try {
2402
2408
  await meeting.join();
@@ -2410,8 +2416,7 @@ describe('plugin-meetings', () => {
2410
2416
  );
2411
2417
  assert.instanceOf(error, CaptchaError);
2412
2418
  assert.equal(error.message, 'bad captcha');
2413
- // should not get to the end promise chain, which does do the join
2414
- assert.notCalled(joinMeetingOptionsSpy);
2419
+ assert.notCalled(MeetingUtil.joinMeeting);
2415
2420
  }
2416
2421
  });
2417
2422
 
@@ -2420,7 +2425,6 @@ describe('plugin-meetings', () => {
2420
2425
  .stub()
2421
2426
  .rejects(new PasswordError('bad password'));
2422
2427
  const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
2423
- const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
2424
2428
 
2425
2429
  try {
2426
2430
  await meeting.join();
@@ -2434,8 +2438,7 @@ describe('plugin-meetings', () => {
2434
2438
  );
2435
2439
  assert.instanceOf(error, PasswordError);
2436
2440
  assert.equal(error.message, 'bad password');
2437
- // should not get to the end promise chain, which does do the join
2438
- assert.notCalled(joinMeetingOptionsSpy);
2441
+ assert.notCalled(MeetingUtil.joinMeeting);
2439
2442
  }
2440
2443
  });
2441
2444
 
@@ -2444,7 +2447,6 @@ describe('plugin-meetings', () => {
2444
2447
  .stub()
2445
2448
  .rejects(new PermissionError('bad permission'));
2446
2449
  const stateMachineFailSpy = sinon.spy(meeting.meetingFiniteStateMachine, 'fail');
2447
- const joinMeetingOptionsSpy = sinon.spy(MeetingUtil.joinMeetingOptions);
2448
2450
 
2449
2451
  try {
2450
2452
  await meeting.join();
@@ -2458,14 +2460,14 @@ describe('plugin-meetings', () => {
2458
2460
  );
2459
2461
  assert.instanceOf(error, PermissionError);
2460
2462
  assert.equal(error.message, 'bad permission');
2461
- // should not get to the end promise chain, which does do the join
2462
- assert.notCalled(joinMeetingOptionsSpy);
2463
+ assert.notCalled(MeetingUtil.joinMeeting);
2463
2464
  }
2464
2465
  });
2465
2466
  });
2466
2467
  });
2467
2468
  });
2468
2469
 
2470
+
2469
2471
  describe('#addMedia', () => {
2470
2472
  const muteStateStub = {
2471
2473
  handleClientRequest: sinon.stub().returns(Promise.resolve(true)),
@@ -9214,8 +9216,8 @@ describe('plugin-meetings', () => {
9214
9216
  const fakeMultistreamRoapMediaConnection = {
9215
9217
  createSendSlot: () => {
9216
9218
  return {
9217
- setCodecParameters: sinon.stub().resolves(),
9218
- deleteCodecParameters: sinon.stub().resolves(),
9219
+ setCustomCodecParameters: sinon.stub().resolves(),
9220
+ markCustomCodecParametersForDeletion: sinon.stub().resolves(),
9219
9221
  };
9220
9222
  },
9221
9223
  };
@@ -9238,27 +9240,29 @@ describe('plugin-meetings', () => {
9238
9240
  }
9239
9241
  );
9240
9242
 
9241
- it('should set the codec parameters when shouldEnableMusicMode is true', async () => {
9243
+ it('should set custom codec parameters when shouldEnableMusicMode is true', async () => {
9242
9244
  await meeting.enableMusicMode(true);
9243
9245
  assert.calledOnceWithExactly(
9244
- meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCodecParameters,
9246
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters,
9247
+ MediaCodecMimeType.OPUS,
9245
9248
  {
9246
9249
  maxaveragebitrate: '64000',
9247
9250
  maxplaybackrate: '48000',
9248
9251
  }
9249
9252
  );
9250
9253
  assert.notCalled(
9251
- meeting.sendSlotManager.getSlot(MediaType.AudioMain).deleteCodecParameters
9254
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion
9252
9255
  );
9253
9256
  });
9254
9257
 
9255
- it('should set the codec parameters when shouldEnableMusicMode is false', async () => {
9258
+ it('should mark custom codec parameters for deletion when shouldEnableMusicMode is false', async () => {
9256
9259
  await meeting.enableMusicMode(false);
9257
9260
  assert.calledOnceWithExactly(
9258
- meeting.sendSlotManager.getSlot(MediaType.AudioMain).deleteCodecParameters,
9261
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).markCustomCodecParametersForDeletion,
9262
+ MediaCodecMimeType.OPUS,
9259
9263
  ['maxaveragebitrate', 'maxplaybackrate']
9260
9264
  );
9261
- assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCodecParameters);
9265
+ assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCustomCodecParameters);
9262
9266
  });
9263
9267
  });
9264
9268
 
@@ -10413,14 +10417,24 @@ describe('plugin-meetings', () => {
10413
10417
  );
10414
10418
  done();
10415
10419
  });
10416
- it('listens to the self admitted guest event', (done) => {
10420
+ it('listens to the self admitted guest event without blocking on token prefetch', async () => {
10417
10421
  meeting.stopKeepAlive = sinon.stub();
10418
10422
  meeting.updateLLMConnection = sinon.stub();
10423
+ let resolvePrefetch;
10424
+
10425
+ meeting.ensureDefaultDatachannelTokenAfterAdmit = sinon
10426
+ .stub()
10427
+ .returns(new Promise((resolve) => {
10428
+ resolvePrefetch = resolve;
10429
+ }));
10419
10430
  meeting.rtcMetrics = {
10420
10431
  sendNextMetrics: sinon.stub(),
10421
10432
  };
10433
+
10422
10434
  meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
10435
+
10423
10436
  assert.calledOnceWithExactly(meeting.stopKeepAlive);
10437
+ assert.calledOnceWithExactly(meeting.ensureDefaultDatachannelTokenAfterAdmit);
10424
10438
  assert.calledThrice(TriggerProxy.trigger);
10425
10439
  assert.calledWith(
10426
10440
  TriggerProxy.trigger,
@@ -10439,7 +10453,11 @@ describe('plugin-meetings', () => {
10439
10453
  correlation_id: meeting.correlationId,
10440
10454
  }
10441
10455
  );
10442
- done();
10456
+
10457
+ resolvePrefetch(false);
10458
+ await Promise.resolve();
10459
+
10460
+ assert.calledOnce(meeting.updateLLMConnection);
10443
10461
  });
10444
10462
 
10445
10463
  it('listens to the breakouts changed event', () => {