@webex/plugin-meetings 3.8.1-next.3 → 3.8.1-next.30

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 (79) hide show
  1. package/README.md +26 -13
  2. package/dist/breakouts/breakout.js +1 -1
  3. package/dist/breakouts/index.js +1 -1
  4. package/dist/constants.js +21 -2
  5. package/dist/constants.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/locus-info/index.js +36 -17
  9. package/dist/locus-info/index.js.map +1 -1
  10. package/dist/media/index.js +2 -2
  11. package/dist/media/index.js.map +1 -1
  12. package/dist/meeting/brbState.js +14 -12
  13. package/dist/meeting/brbState.js.map +1 -1
  14. package/dist/meeting/index.js +169 -66
  15. package/dist/meeting/index.js.map +1 -1
  16. package/dist/meeting/request.js +19 -0
  17. package/dist/meeting/request.js.map +1 -1
  18. package/dist/meeting/request.type.js.map +1 -1
  19. package/dist/meetings/index.js +35 -33
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/members/index.js +8 -6
  22. package/dist/members/index.js.map +1 -1
  23. package/dist/members/request.js +3 -3
  24. package/dist/members/request.js.map +1 -1
  25. package/dist/members/util.js +18 -6
  26. package/dist/members/util.js.map +1 -1
  27. package/dist/multistream/mediaRequestManager.js +1 -1
  28. package/dist/multistream/mediaRequestManager.js.map +1 -1
  29. package/dist/multistream/remoteMedia.js +34 -5
  30. package/dist/multistream/remoteMedia.js.map +1 -1
  31. package/dist/multistream/remoteMediaGroup.js +42 -2
  32. package/dist/multistream/remoteMediaGroup.js.map +1 -1
  33. package/dist/multistream/sendSlotManager.js +32 -2
  34. package/dist/multistream/sendSlotManager.js.map +1 -1
  35. package/dist/reachability/index.js +5 -10
  36. package/dist/reachability/index.js.map +1 -1
  37. package/dist/types/constants.d.ts +19 -0
  38. package/dist/types/meeting/brbState.d.ts +0 -1
  39. package/dist/types/meeting/index.d.ts +28 -4
  40. package/dist/types/meeting/request.d.ts +9 -1
  41. package/dist/types/meeting/request.type.d.ts +74 -0
  42. package/dist/types/members/index.d.ts +8 -3
  43. package/dist/types/members/request.d.ts +1 -1
  44. package/dist/types/members/util.d.ts +5 -2
  45. package/dist/types/multistream/remoteMedia.d.ts +20 -1
  46. package/dist/types/multistream/remoteMediaGroup.d.ts +11 -0
  47. package/dist/types/multistream/sendSlotManager.d.ts +16 -0
  48. package/dist/types/reachability/index.d.ts +2 -2
  49. package/dist/webinar/index.js +1 -1
  50. package/package.json +24 -24
  51. package/src/constants.ts +20 -0
  52. package/src/locus-info/index.ts +47 -20
  53. package/src/media/index.ts +2 -2
  54. package/src/meeting/brbState.ts +9 -7
  55. package/src/meeting/index.ts +126 -23
  56. package/src/meeting/request.ts +16 -0
  57. package/src/meeting/request.type.ts +64 -0
  58. package/src/meetings/index.ts +3 -2
  59. package/src/members/index.ts +7 -5
  60. package/src/members/request.ts +2 -2
  61. package/src/members/util.ts +14 -3
  62. package/src/multistream/mediaRequestManager.ts +7 -7
  63. package/src/multistream/remoteMedia.ts +34 -4
  64. package/src/multistream/remoteMediaGroup.ts +37 -2
  65. package/src/multistream/sendSlotManager.ts +34 -2
  66. package/src/reachability/index.ts +5 -13
  67. package/test/unit/spec/locus-info/index.js +177 -47
  68. package/test/unit/spec/media/index.ts +107 -0
  69. package/test/unit/spec/meeting/brbState.ts +9 -9
  70. package/test/unit/spec/meeting/index.js +606 -55
  71. package/test/unit/spec/meeting/request.js +71 -0
  72. package/test/unit/spec/meetings/index.js +1 -0
  73. package/test/unit/spec/members/index.js +32 -9
  74. package/test/unit/spec/members/request.js +2 -2
  75. package/test/unit/spec/members/utils.js +27 -7
  76. package/test/unit/spec/multistream/mediaRequestManager.ts +19 -6
  77. package/test/unit/spec/multistream/remoteMedia.ts +66 -2
  78. package/test/unit/spec/multistream/sendSlotManager.ts +59 -0
  79. package/test/unit/spec/reachability/index.ts +2 -6
@@ -7,10 +7,20 @@ import {
7
7
  StreamState,
8
8
  } from '@webex/internal-media-core';
9
9
 
10
+ /**
11
+ * This class is used to manage the sendSlots for the given media types.
12
+ */
10
13
  export default class SendSlotManager {
11
14
  private readonly slots: Map<MediaType, SendSlot> = new Map();
12
15
  private readonly LoggerProxy: any;
16
+ private readonly sourceStateOverrides: Map<MediaType, StreamState> = new Map();
13
17
 
18
+ /**
19
+ * Constructor for SendSlotManager
20
+ *
21
+ * @param {any} LoggerProxy is used to log the messages
22
+ * @constructor
23
+ */
14
24
  constructor(LoggerProxy: any) {
15
25
  this.LoggerProxy = LoggerProxy;
16
26
  }
@@ -93,7 +103,7 @@ export default class SendSlotManager {
93
103
  public setSourceStateOverride(mediaType: MediaType, state: StreamState | null) {
94
104
  if (mediaType !== MediaType.VideoMain) {
95
105
  throw new Error(
96
- `sendSlotManager cannot set source state override which media type is ${mediaType}`
106
+ `Invalid media type '${mediaType}'. Source state overrides are only applicable to ${MediaType.VideoMain}.`
97
107
  );
98
108
  }
99
109
 
@@ -103,17 +113,39 @@ export default class SendSlotManager {
103
113
  throw new Error(`Slot for ${mediaType} does not exist`);
104
114
  }
105
115
 
116
+ const currentStateOverride = this.getSourceStateOverride(mediaType);
117
+ if (currentStateOverride === state) {
118
+ return;
119
+ }
120
+
106
121
  if (state) {
107
122
  slot.setSourceStateOverride(state);
123
+ this.sourceStateOverrides.set(mediaType, state);
108
124
  } else {
109
125
  slot.clearSourceStateOverride();
126
+ this.sourceStateOverrides.delete(mediaType);
110
127
  }
111
128
 
112
129
  this.LoggerProxy.logger.info(
113
- `SendSlotsManager->setSourceStateOverride#set source state override for ${mediaType} to ${state}`
130
+ `SendSlotManager->setSourceStateOverride#set source state override for ${mediaType} to ${state}`
114
131
  );
115
132
  }
116
133
 
134
+ /**
135
+ * Gets the source state override for the given media type.
136
+ * @param {MediaType} mediaType - The type of media to get the source state override for.
137
+ * @returns {StreamState | null} - The current source state override or null if not set.
138
+ */
139
+ private getSourceStateOverride(mediaType: MediaType): StreamState | null {
140
+ if (mediaType !== MediaType.VideoMain) {
141
+ throw new Error(
142
+ `Invalid media type '${mediaType}'. Source state overrides are only applicable to ${MediaType.VideoMain}.`
143
+ );
144
+ }
145
+
146
+ return this.sourceStateOverrides.get(mediaType) || null;
147
+ }
148
+
117
149
  /**
118
150
  * This method publishes the given stream to the sendSlot for the given mediaType
119
151
  * @param {MediaType} mediaType MediaType of the sendSlot to which a stream needs to be published (AUDIO_MAIN/VIDEO_MAIN/AUDIO_SLIDES/VIDEO_SLIDES)
@@ -140,22 +140,14 @@ export default class Reachability extends EventsScope {
140
140
 
141
141
  /**
142
142
  * Checks if the given subnet is reachable
143
- * @param {string} mediaServerIp - media server ip
143
+ * @param {string} selectedSubnetFirstOctet - selected subnet first octet, e.g. "10" for "10.X.X.X"
144
144
  * @returns {boolean | null} true if reachable, false if not reachable, null if mediaServerIp is not provided
145
145
  * @public
146
146
  * @memberof Reachability
147
147
  */
148
- public isSubnetReachable(mediaServerIp?: string): boolean | null {
149
- if (!mediaServerIp) {
150
- LoggerProxy.logger.error(`Reachability:index#isSubnetReachable --> mediaServerIp is null`);
151
-
152
- return null;
153
- }
154
-
155
- const subnetFirstOctet = mediaServerIp.split('.')[0];
156
-
148
+ public isSubnetReachable(selectedSubnetFirstOctet: string): boolean | null {
157
149
  LoggerProxy.logger.info(
158
- `Reachability:index#isSubnetReachable --> Looking for subnet: ${subnetFirstOctet}.X.X.X`
150
+ `Reachability:index#isSubnetReachable --> Looking for subnet: ${selectedSubnetFirstOctet}.X.X.X`
159
151
  );
160
152
 
161
153
  const matchingReachedClusters = Object.values(this.clusterReachability).reduce(
@@ -167,7 +159,7 @@ export default class Reachability extends EventsScope {
167
159
  const subnet = reachedSubnetsArray[i];
168
160
  const reachedSubnetFirstOctet = subnet.split('.')[0];
169
161
 
170
- if (subnetFirstOctet === reachedSubnetFirstOctet) {
162
+ if (selectedSubnetFirstOctet === reachedSubnetFirstOctet) {
171
163
  acc.add(cluster.name);
172
164
  }
173
165
 
@@ -186,7 +178,7 @@ export default class Reachability extends EventsScope {
186
178
  );
187
179
 
188
180
  LoggerProxy.logger.info(
189
- `Reachability:index#isSubnetReachable --> Found ${matchingReachedClusters.size} clusters that use the subnet ${subnetFirstOctet}.X.X.X`
181
+ `Reachability:index#isSubnetReachable --> Found ${matchingReachedClusters.size} clusters that use the subnet ${selectedSubnetFirstOctet}.X.X.X`
190
182
  );
191
183
 
192
184
  return matchingReachedClusters.size > 0;
@@ -305,7 +305,7 @@ describe('plugin-meetings', () => {
305
305
  {state: newControls.rdcControl}
306
306
  );
307
307
  });
308
-
308
+
309
309
  it('should trigger the CONTROLS_POLLING_QA_CHANGED event when necessary', () => {
310
310
  locusInfo.controls = {};
311
311
  locusInfo.emitScoped = sinon.stub();
@@ -2108,6 +2108,38 @@ describe('plugin-meetings', () => {
2108
2108
  assert.isFunction(locusParser.onDeltaAction);
2109
2109
  });
2110
2110
 
2111
+ it("#updateLocusInfo invokes updateLocusUrl before updateMeetingInfo", () => {
2112
+ const callOrder = [];
2113
+ sinon.stub(locusInfo, "updateControls");
2114
+ sinon.stub(locusInfo, "updateConversationUrl");
2115
+ sinon.stub(locusInfo, "updateCreated");
2116
+ sinon.stub(locusInfo, "updateFullState");
2117
+ sinon.stub(locusInfo, "updateHostInfo");
2118
+ sinon.stub(locusInfo, "updateMeetingInfo").callsFake(() => {
2119
+ callOrder.push("updateMeetingInfo");
2120
+ });
2121
+ sinon.stub(locusInfo, "updateMediaShares");
2122
+ sinon.stub(locusInfo, "updateParticipantsUrl");
2123
+ sinon.stub(locusInfo, "updateReplace");
2124
+ sinon.stub(locusInfo, "updateSelf");
2125
+ sinon.stub(locusInfo, "updateLocusUrl").callsFake(() => {
2126
+ callOrder.push("updateLocusUrl");
2127
+ });
2128
+ sinon.stub(locusInfo, "updateAclUrl");
2129
+ sinon.stub(locusInfo, "updateBasequence");
2130
+ sinon.stub(locusInfo, "updateSequence");
2131
+ sinon.stub(locusInfo, "updateMemberShip");
2132
+ sinon.stub(locusInfo, "updateIdentifiers");
2133
+ sinon.stub(locusInfo, "updateEmbeddedApps");
2134
+ sinon.stub(locusInfo, "updateResources");
2135
+ sinon.stub(locusInfo, "compareAndUpdate");
2136
+
2137
+ locusInfo.updateLocusInfo(locus);
2138
+
2139
+ // Ensure updateLocusUrl is called before updateMeetingInfo if both are called
2140
+ assert.deepEqual(callOrder, ['updateLocusUrl', 'updateMeetingInfo']);
2141
+ });
2142
+
2111
2143
  it('#updateLocusInfo ignores breakout LEFT message', () => {
2112
2144
  const newLocus = {
2113
2145
  self: {
@@ -2159,6 +2191,8 @@ describe('plugin-meetings', () => {
2159
2191
  assert.notCalled(locusInfo.compareAndUpdate);
2160
2192
  });
2161
2193
 
2194
+
2195
+
2162
2196
  it('onFullLocus() updates the working-copy of locus parser', () => {
2163
2197
  const eventType = 'fakeEvent';
2164
2198
 
@@ -2257,7 +2291,7 @@ describe('plugin-meetings', () => {
2257
2291
 
2258
2292
  it('applyLocusDeltaData gets delta locus on DESYNC action if we have a syncUrl', () => {
2259
2293
  const {DESYNC} = LocusDeltaParser.loci;
2260
- const fakeDeltaLocus = {id: 'fake delta locus'};
2294
+ const fakeDeltaLocus = {baseSequence: {}, id: 'fake delta locus'};
2261
2295
  const meeting = {
2262
2296
  meetingRequest: {
2263
2297
  getLocusDTO: sandbox.stub().resolves({body: fakeDeltaLocus}),
@@ -2392,25 +2426,22 @@ describe('plugin-meetings', () => {
2392
2426
  };
2393
2427
  });
2394
2428
 
2395
- it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', () => {
2429
+ it('applyLocusDeltaData gets full locus on DESYNC action if we do not have a syncUrl and destroys the meeting if that fails', async () => {
2396
2430
  meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
2397
2431
 
2398
2432
  locusInfo.locusParser.workingCopy = {}; // no syncUrl
2399
2433
 
2400
- // Since we have a promise inside a function we want to test that's not returned,
2401
- // we will wait and stub it's last function to resolve this waiting promise.
2402
- return new Promise((resolve) => {
2403
- webex.meetings.destroy.callsFake(() => resolve());
2404
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2405
- }).then(() => {
2406
- assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
2434
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2407
2435
 
2408
- assert.notCalled(meeting.locusInfo.handleLocusDelta);
2409
- assert.notCalled(meeting.locusInfo.onFullLocus);
2410
- assert.notCalled(locusInfo.locusParser.resume);
2436
+ await testUtils.flushPromises();
2411
2437
 
2412
- assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2413
- });
2438
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'fullSyncUrl'});
2439
+
2440
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2441
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2442
+ assert.notCalled(locusInfo.locusParser.resume);
2443
+
2444
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2414
2445
  });
2415
2446
 
2416
2447
  it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails, does a full locus sync', () => {
@@ -2447,39 +2478,62 @@ describe('plugin-meetings', () => {
2447
2478
  });
2448
2479
  });
2449
2480
 
2450
- it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', () => {
2481
+ it('applyLocusDeltaData first tries a delta sync on DESYNC action and if that fails with 403, it does not do a full locus sync', async () => {
2482
+ const fake403Error = new Error('fake error');
2483
+ fake403Error.statusCode = 403;
2484
+
2485
+ meeting.meetingRequest.getLocusDTO.onCall(0).rejects(fake403Error);
2486
+
2487
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2488
+
2489
+ await testUtils.flushPromises();
2490
+
2491
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {url: 'deltaSyncUrl'});
2492
+
2493
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2494
+ correlationId: meeting.correlationId,
2495
+ url: 'deltaSyncUrl',
2496
+ reason: 'fake error',
2497
+ errorName: 'Error',
2498
+ stack: sinon.match.any,
2499
+ code: sinon.match.any,
2500
+ });
2501
+
2502
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2503
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2504
+ assert.notCalled(locusInfo.locusParser.resume);
2505
+ });
2506
+
2507
+ it('applyLocusDeltaData destroys the meeting if both delta sync and full sync fail', async () => {
2451
2508
  meeting.meetingRequest.getLocusDTO.rejects(new Error('fake error'));
2452
2509
 
2453
- // Since we have a promise inside a function we want to test that's not returned,
2454
- // we will wait and stub it's last function to resolve this waiting promise.
2455
- return new Promise((resolve) => {
2456
- webex.meetings.destroy.callsFake(() => resolve());
2457
- locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2458
- }).then(() => {
2459
- assert.calledTwice(meeting.meetingRequest.getLocusDTO);
2510
+ locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2460
2511
 
2461
- assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [
2462
- {url: 'deltaSyncUrl'},
2463
- ]);
2464
- assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
2465
- {url: 'fullSyncUrl'},
2466
- ]);
2512
+ await testUtils.flushPromises();
2467
2513
 
2468
- assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2469
- correlationId: meeting.correlationId,
2470
- url: 'deltaSyncUrl',
2471
- reason: 'fake error',
2472
- errorName: 'Error',
2473
- stack: sinon.match.any,
2474
- code: sinon.match.any,
2475
- });
2514
+ assert.calledTwice(meeting.meetingRequest.getLocusDTO);
2476
2515
 
2477
- assert.notCalled(meeting.locusInfo.handleLocusDelta);
2478
- assert.notCalled(meeting.locusInfo.onFullLocus);
2479
- assert.notCalled(locusInfo.locusParser.resume);
2516
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[0].args, [
2517
+ {url: 'deltaSyncUrl'},
2518
+ ]);
2519
+ assert.deepEqual(meeting.meetingRequest.getLocusDTO.getCalls()[1].args, [
2520
+ {url: 'fullSyncUrl'},
2521
+ ]);
2480
2522
 
2481
- assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2523
+ assert.calledWith(sendBehavioralMetricStub, 'js_sdk_locus_delta_sync_failed', {
2524
+ correlationId: meeting.correlationId,
2525
+ url: 'deltaSyncUrl',
2526
+ reason: 'fake error',
2527
+ errorName: 'Error',
2528
+ stack: sinon.match.any,
2529
+ code: sinon.match.any,
2482
2530
  });
2531
+
2532
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2533
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2534
+ assert.notCalled(locusInfo.locusParser.resume);
2535
+
2536
+ assert.calledOnceWithExactly(webex.meetings.destroy, meeting, 'LOCUS_DTO_SYNC_FAILED');
2483
2537
  });
2484
2538
  });
2485
2539
 
@@ -2509,9 +2563,7 @@ describe('plugin-meetings', () => {
2509
2563
  });
2510
2564
 
2511
2565
  it('onDeltaLocus merges delta participants with existing participants', () => {
2512
- const FAKE_DELTA_PARTICIPANTS = [
2513
- {id: '1111'}, {id: '2222'}
2514
- ]
2566
+ const FAKE_DELTA_PARTICIPANTS = [{id: '1111'}, {id: '2222'}];
2515
2567
  fakeLocus.participants = FAKE_DELTA_PARTICIPANTS;
2516
2568
 
2517
2569
  sinon.spy(locusInfo, 'mergeParticipants');
@@ -2519,9 +2571,87 @@ describe('plugin-meetings', () => {
2519
2571
  const existingParticipants = locusInfo.participants;
2520
2572
 
2521
2573
  locusInfo.onDeltaLocus(fakeLocus);
2522
- assert.calledOnceWithExactly(locusInfo.mergeParticipants, existingParticipants, FAKE_DELTA_PARTICIPANTS);
2574
+ assert.calledOnceWithExactly(
2575
+ locusInfo.mergeParticipants,
2576
+ existingParticipants,
2577
+ FAKE_DELTA_PARTICIPANTS
2578
+ );
2523
2579
  assert.calledWith(locusInfo.updateParticipants, FAKE_DELTA_PARTICIPANTS, false);
2524
2580
  });
2581
+
2582
+ [true, false].forEach((isDelta) =>
2583
+ it(`applyLocusDeltaData - handles empty ${
2584
+ isDelta ? 'delta' : 'full'
2585
+ } DTO in response`, async () => {
2586
+ const {DESYNC} = LocusDeltaParser.loci;
2587
+ const fakeFullLocusDto = {};
2588
+ const meeting = {
2589
+ meetingRequest: {
2590
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
2591
+ },
2592
+ locusInfo: {
2593
+ onFullLocus: sandbox.stub(),
2594
+ handleLocusDelta: sandbox.stub(),
2595
+ },
2596
+ locusUrl: 'fake locus FULL url',
2597
+ };
2598
+
2599
+ sinon.stub(locusInfo.locusParser, 'resume').resolves();
2600
+
2601
+ if (isDelta) {
2602
+ locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
2603
+ } else {
2604
+ locusInfo.locusParser.workingCopy = {}; // no syncUrl (to trigger FULL DTO request)
2605
+ }
2606
+
2607
+ await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2608
+
2609
+ await testUtils.flushPromises();
2610
+
2611
+ if (isDelta) {
2612
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2613
+ url: 'fake locus DELTA url',
2614
+ });
2615
+ } else {
2616
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2617
+ url: 'fake locus FULL url',
2618
+ });
2619
+ }
2620
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2621
+ assert.notCalled(meeting.locusInfo.onFullLocus);
2622
+ assert.calledOnce(locusInfo.locusParser.resume);
2623
+ })
2624
+ );
2625
+
2626
+ it(`applyLocusDeltaData - handles the case when we get FULL DTO when we asked for DELTA DTO`, async () => {
2627
+ const {DESYNC} = LocusDeltaParser.loci;
2628
+ const fakeFullLocusDto = {someStuff: 'data'}; // non-empty DTO, without baseSequence
2629
+ const meeting = {
2630
+ meetingRequest: {
2631
+ getLocusDTO: sandbox.stub().resolves({body: fakeFullLocusDto}),
2632
+ },
2633
+ locusInfo: {
2634
+ onFullLocus: sandbox.stub(),
2635
+ handleLocusDelta: sandbox.stub(),
2636
+ },
2637
+ locusUrl: 'fake locus FULL url',
2638
+ };
2639
+
2640
+ sinon.stub(locusInfo.locusParser, 'resume').resolves();
2641
+
2642
+ locusInfo.locusParser.workingCopy = {syncUrl: 'fake locus DELTA url'};
2643
+
2644
+ await locusInfo.applyLocusDeltaData(DESYNC, fakeLocus, meeting);
2645
+
2646
+ await testUtils.flushPromises();
2647
+
2648
+ assert.calledOnceWithExactly(meeting.meetingRequest.getLocusDTO, {
2649
+ url: 'fake locus DELTA url',
2650
+ });
2651
+ assert.notCalled(meeting.locusInfo.handleLocusDelta);
2652
+ assert.calledOnceWithExactly(meeting.locusInfo.onFullLocus, fakeFullLocusDto);
2653
+ assert.calledOnce(locusInfo.locusParser.resume);
2654
+ });
2525
2655
  });
2526
2656
 
2527
2657
  describe('#updateLocusCache', () => {
@@ -2936,8 +3066,8 @@ describe('plugin-meetings', () => {
2936
3066
 
2937
3067
  sinon.stub(locusInfo, 'updateParticipantDeltas');
2938
3068
  sinon.stub(locusInfo, 'updateParticipants');
2939
- sinon.stub(locusInfo, 'isMeetingActive'),
2940
- sinon.stub(locusInfo, 'handleOneOnOneEvent'),
3069
+ sinon.stub(locusInfo, 'isMeetingActive');
3070
+ sinon.stub(locusInfo, 'handleOneOnOneEvent');
2941
3071
  (updateLocusInfoStub = sinon.stub(locusInfo, 'updateLocusInfo'));
2942
3072
  syncRequestStub = sinon.stub().resolves({body: {}});
2943
3073
 
@@ -137,6 +137,113 @@ describe('createMediaConnection', () => {
137
137
  );
138
138
  });
139
139
 
140
+ it('should set direction to sendonly for both audio and video when only send flags are true', () => {
141
+ const roapMediaConnectionConstructorStub = sinon
142
+ .stub(InternalMediaCoreModule, 'RoapMediaConnection')
143
+ .returns(fakeRoapMediaConnection);
144
+
145
+ StaticConfig.set({bandwidth: {audio: 123, video: 456, startBitrate: 999}});
146
+
147
+ const ENABLE_EXTMAP = false;
148
+ const ENABLE_RTX = true;
149
+
150
+ Media.createMediaConnection(false, 'sendonly-debug-id', 'meetingId', {
151
+ mediaProperties: {
152
+ mediaDirection: {
153
+ sendAudio: true,
154
+ receiveAudio: false,
155
+ sendVideo: true,
156
+ receiveVideo: false,
157
+ sendShare: false,
158
+ receiveShare: false,
159
+ },
160
+ audioStream: fakeAudioStream,
161
+ videoStream: fakeVideoStream,
162
+ shareVideoTrack: null,
163
+ shareAudioTrack: null,
164
+ },
165
+ remoteQualityLevel: 'HIGH',
166
+ enableRtx: ENABLE_RTX,
167
+ enableExtmap: ENABLE_EXTMAP,
168
+ turnServerInfo: undefined,
169
+ iceCandidatesTimeout: undefined,
170
+ });
171
+
172
+ assert.calledWith(
173
+ roapMediaConnectionConstructorStub,
174
+ sinon.match.any,
175
+ {
176
+ localTracks: {
177
+ audio: fakeTrack,
178
+ video: fakeTrack,
179
+ screenShareVideo: undefined,
180
+ screenShareAudio: undefined,
181
+ },
182
+ direction: {
183
+ audio: 'sendonly',
184
+ video: 'sendonly',
185
+ screenShareVideo: 'inactive',
186
+ },
187
+ remoteQualityLevel: 'HIGH',
188
+ },
189
+ 'sendonly-debug-id'
190
+ );
191
+ });
192
+
193
+ it('should set direction to recvonly for both audio and video when only receive flags are true', () => {
194
+ const roapMediaConnectionConstructorStub = sinon
195
+ .stub(InternalMediaCoreModule, 'RoapMediaConnection')
196
+ .returns(fakeRoapMediaConnection);
197
+
198
+ StaticConfig.set({bandwidth: {audio: 123, video: 456, startBitrate: 999}});
199
+
200
+ const ENABLE_EXTMAP = true;
201
+ const ENABLE_RTX = false;
202
+
203
+ Media.createMediaConnection(false, 'recvonly-debug-id', 'meetingId', {
204
+ mediaProperties: {
205
+ mediaDirection: {
206
+ sendAudio: false,
207
+ receiveAudio: true,
208
+ sendVideo: false,
209
+ receiveVideo: true,
210
+ sendShare: false,
211
+ receiveShare: false,
212
+ },
213
+ audioStream: fakeAudioStream,
214
+ videoStream: fakeVideoStream,
215
+ shareVideoTrack: null,
216
+ shareAudioTrack: null,
217
+ },
218
+ remoteQualityLevel: 'HIGH',
219
+ enableRtx: ENABLE_RTX,
220
+ enableExtmap: ENABLE_EXTMAP,
221
+ turnServerInfo: undefined,
222
+ iceCandidatesTimeout: undefined,
223
+ });
224
+
225
+ assert.calledWith(
226
+ roapMediaConnectionConstructorStub,
227
+ sinon.match.any,
228
+ {
229
+ localTracks: {
230
+ audio: fakeTrack,
231
+ video: fakeTrack,
232
+ screenShareVideo: undefined,
233
+ screenShareAudio: undefined,
234
+ },
235
+ direction: {
236
+ audio: 'recvonly',
237
+ video: 'recvonly',
238
+ screenShareVideo: 'inactive',
239
+ },
240
+ remoteQualityLevel: 'HIGH',
241
+ },
242
+ 'recvonly-debug-id'
243
+ );
244
+ });
245
+
246
+
140
247
  it('creates a MultistreamRoapMediaConnection when multistream is enabled', () => {
141
248
  const multistreamRoapMediaConnectionConstructorStub = sinon
142
249
  .stub(InternalMediaCoreModule, 'MultistreamRoapMediaConnection')
@@ -3,12 +3,12 @@ import {assert, expect} from '@webex/test-helper-chai';
3
3
 
4
4
  import testUtils from '../../../utils/testUtils';
5
5
  import {BrbState, createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
6
- import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
6
+ import {MediaType} from '@webex/internal-media-core';
7
7
 
8
8
  describe('plugin-meetings', () => {
9
9
  let meeting: any;
10
10
  let brbState: BrbState;
11
- let setBrbStub: sinon.SinonStub;
11
+ let setBrbStub: sinon.SinonStub;
12
12
 
13
13
  beforeEach(async () => {
14
14
  meeting = {
@@ -23,7 +23,7 @@ describe('plugin-meetings', () => {
23
23
  setSourceStateOverride: sinon.stub(),
24
24
  },
25
25
  meetingRequest: {
26
- setBrb: () => {}
26
+ setBrb: () => {},
27
27
  },
28
28
  };
29
29
 
@@ -104,12 +104,12 @@ describe('plugin-meetings', () => {
104
104
  assert.isTrue(meeting.meetingRequest.setBrb.calledOnce);
105
105
  });
106
106
 
107
- it('sets source state override when client state does not match server state', async () => {
107
+ it('updates source state override', async () => {
108
108
  brbState.enable(true, meeting.sendSlotManager);
109
109
  brbState.handleServerBrbUpdate(true);
110
110
  await testUtils.flushPromises();
111
111
 
112
- assert.isTrue(meeting.sendSlotManager.setSourceStateOverride.calledOnce);
112
+ assert.isTrue(meeting.sendSlotManager.setSourceStateOverride.called);
113
113
  });
114
114
 
115
115
  it('handles server update', async () => {
@@ -141,12 +141,12 @@ describe('plugin-meetings', () => {
141
141
  it('should reject when sendLocalBrbStateToServer fails', async () => {
142
142
  const error = new Error('send failed');
143
143
  setBrbStub.rejects(error);
144
-
145
- await expect(
146
- brbState.enable(true, meeting.sendSlotManager)
147
- ).to.be.rejectedWith(error);
144
+
145
+ const enablePromise = brbState.enable(true, meeting.sendSlotManager);
146
+ await expect(enablePromise).to.be.rejectedWith(error);
148
147
 
149
148
  assert.isFalse(brbState.state.syncToServerInProgress);
149
+ expect(meeting.sendSlotManager.setSourceStateOverride.called).to.be.false;
150
150
  });
151
151
  });
152
152
  });