@webex/plugin-meetings 3.7.0-wxcc.1 → 3.8.0-next.1

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 (52) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +15 -3
  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/selfUtils.js +5 -0
  8. package/dist/locus-info/selfUtils.js.map +1 -1
  9. package/dist/media/MediaConnectionAwaiter.js +1 -0
  10. package/dist/media/MediaConnectionAwaiter.js.map +1 -1
  11. package/dist/media/properties.js +30 -16
  12. package/dist/media/properties.js.map +1 -1
  13. package/dist/meeting/brbState.js +167 -0
  14. package/dist/meeting/brbState.js.map +1 -0
  15. package/dist/meeting/index.js +367 -296
  16. package/dist/meeting/index.js.map +1 -1
  17. package/dist/meeting/muteState.js +1 -6
  18. package/dist/meeting/muteState.js.map +1 -1
  19. package/dist/meeting-info/meeting-info-v2.js +19 -12
  20. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  21. package/dist/meeting-info/utilv2.js +5 -1
  22. package/dist/meeting-info/utilv2.js.map +1 -1
  23. package/dist/metrics/constants.js +2 -0
  24. package/dist/metrics/constants.js.map +1 -1
  25. package/dist/reachability/index.js +31 -3
  26. package/dist/reachability/index.js.map +1 -1
  27. package/dist/types/constants.d.ts +9 -2
  28. package/dist/types/meeting/brbState.d.ts +54 -0
  29. package/dist/types/meeting/index.d.ts +23 -0
  30. package/dist/types/meeting-info/meeting-info-v2.d.ts +3 -1
  31. package/dist/types/metrics/constants.d.ts +2 -0
  32. package/dist/types/reachability/index.d.ts +9 -1
  33. package/dist/webinar/index.js +1 -1
  34. package/package.json +23 -23
  35. package/src/constants.ts +10 -2
  36. package/src/locus-info/selfUtils.ts +5 -0
  37. package/src/media/MediaConnectionAwaiter.ts +2 -0
  38. package/src/media/properties.ts +34 -13
  39. package/src/meeting/brbState.ts +169 -0
  40. package/src/meeting/index.ts +120 -27
  41. package/src/meeting/muteState.ts +1 -6
  42. package/src/meeting-info/meeting-info-v2.ts +9 -1
  43. package/src/meeting-info/utilv2.ts +14 -2
  44. package/src/metrics/constants.ts +2 -0
  45. package/src/reachability/index.ts +29 -1
  46. package/test/unit/spec/locus-info/selfUtils.js +10 -0
  47. package/test/unit/spec/media/properties.ts +15 -0
  48. package/test/unit/spec/meeting/brbState.ts +114 -0
  49. package/test/unit/spec/meeting/index.js +101 -33
  50. package/test/unit/spec/meeting/muteState.js +0 -24
  51. package/test/unit/spec/meeting-info/utilv2.js +9 -0
  52. package/test/unit/spec/reachability/index.ts +120 -10
@@ -17,6 +17,8 @@ const CAPTCHA_ERROR_DEFAULT_MESSAGE =
17
17
  const ADHOC_MEETING_DEFAULT_ERROR =
18
18
  'Failed starting the adhoc meeting, Please contact support team ';
19
19
  const CAPTCHA_ERROR_REQUIRES_PASSWORD_CODES = [423005, 423006];
20
+ const CAPTCHA_ERROR_REQUIRES_REGISTRATION_ID_CODES = [423007];
21
+
20
22
  const POLICY_ERROR_CODES = [403049, 403104, 403103, 403048, 403102, 403101];
21
23
  const JOIN_FORBIDDEN_CODES = [403003];
22
24
  /**
@@ -113,6 +115,7 @@ export class MeetingInfoV2PolicyError extends Error {
113
115
  export class MeetingInfoV2CaptchaError extends Error {
114
116
  captchaInfo: any;
115
117
  isPasswordRequired: any;
118
+ isRegistrationIdRequired: any;
116
119
  sdkMessage: any;
117
120
  wbxAppApiCode: any;
118
121
  body: any;
@@ -134,6 +137,8 @@ export class MeetingInfoV2CaptchaError extends Error {
134
137
  this.stack = new Error().stack;
135
138
  this.wbxAppApiCode = wbxAppApiErrorCode;
136
139
  this.isPasswordRequired = CAPTCHA_ERROR_REQUIRES_PASSWORD_CODES.includes(wbxAppApiErrorCode);
140
+ this.isRegistrationIdRequired =
141
+ CAPTCHA_ERROR_REQUIRES_REGISTRATION_ID_CODES.includes(wbxAppApiErrorCode);
137
142
  this.captchaInfo = captchaInfo;
138
143
  }
139
144
  }
@@ -370,6 +375,7 @@ export default class MeetingInfoV2 {
370
375
  * @param {String} locusId
371
376
  * @param {Object} extraParams
372
377
  * @param {Object} options
378
+ * @param {String} registrationId
373
379
  * @returns {Promise} returns a meeting info object
374
380
  * @public
375
381
  * @memberof MeetingInfo
@@ -385,7 +391,8 @@ export default class MeetingInfoV2 {
385
391
  installedOrgID = null,
386
392
  locusId = null,
387
393
  extraParams: object = {},
388
- options: {meetingId?: string; sendCAevents?: boolean} = {}
394
+ options: {meetingId?: string; sendCAevents?: boolean} = {},
395
+ registrationId: string = null
389
396
  ) {
390
397
  const {meetingId, sendCAevents} = options;
391
398
 
@@ -410,6 +417,7 @@ export default class MeetingInfoV2 {
410
417
  installedOrgID,
411
418
  locusId,
412
419
  extraParams,
420
+ registrationId,
413
421
  });
414
422
 
415
423
  // If the body only contains the default properties, we don't have enough to
@@ -228,8 +228,16 @@ export default class MeetingInfoUtil {
228
228
  * @returns {Object} returns an object with {resource, method}
229
229
  */
230
230
  static getRequestBody(options: {type: DESTINATION_TYPE; destination: object} | any) {
231
- const {type, destination, password, captchaInfo, installedOrgID, locusId, extraParams} =
232
- options;
231
+ const {
232
+ type,
233
+ destination,
234
+ password,
235
+ captchaInfo,
236
+ installedOrgID,
237
+ locusId,
238
+ extraParams,
239
+ registrationId,
240
+ } = options;
233
241
  const body: any = {
234
242
  ...DEFAULT_MEETING_INFO_REQUEST_BODY,
235
243
  ...extraParams,
@@ -271,6 +279,10 @@ export default class MeetingInfoUtil {
271
279
  body.password = password;
272
280
  }
273
281
 
282
+ if (registrationId) {
283
+ body.registrationId = registrationId;
284
+ }
285
+
274
286
  if (captchaInfo) {
275
287
  body.captchaID = captchaInfo.id;
276
288
  body.captchaVerifyCode = captchaInfo.code;
@@ -73,6 +73,8 @@ const BEHAVIORAL_METRICS = {
73
73
  JOIN_WEBINAR_ERROR: 'js_sdk_join_webinar_error',
74
74
  GUEST_ENTERED_LOBBY: 'js_sdk_guest_entered_lobby',
75
75
  GUEST_EXITED_LOBBY: 'js_sdk_guest_exited_lobby',
76
+ VERIFY_REGISTRATION_ID_SUCCESS: 'js_sdk_verify_registrationId_success',
77
+ VERIFY_REGISTRATION_ID_ERROR: 'js_sdk_verify_registrationId_error',
76
78
  JOIN_FORBIDDEN_ERROR: 'js_sdk_join_forbidden_error',
77
79
  };
78
80
 
@@ -259,6 +259,32 @@ export default class Reachability extends EventsScope {
259
259
  }
260
260
  }
261
261
 
262
+ /**
263
+ * Stops all reachability checks that are in progress
264
+ * @public
265
+ * @memberof Reachability
266
+ * @returns {void}
267
+ */
268
+ public stopReachability() {
269
+ // overallTimer is always there only if there is reachability in progress
270
+ if (this.overallTimer) {
271
+ LoggerProxy.logger.log(
272
+ 'Reachability:index#stopReachability --> stopping reachability checks'
273
+ );
274
+ this.abortCurrentChecks();
275
+ this.emit(
276
+ {
277
+ file: 'reachability',
278
+ function: 'stopReachability',
279
+ },
280
+ 'reachability:stopped',
281
+ {}
282
+ );
283
+ this.sendMetric(true);
284
+ this.resolveReachabilityPromise();
285
+ }
286
+ }
287
+
262
288
  /**
263
289
  * Returns statistics about last reachability results. The returned value is an object
264
290
  * with a flat list of properties so that it can be easily sent with metrics
@@ -637,9 +663,10 @@ export default class Reachability extends EventsScope {
637
663
  /**
638
664
  * Sends a metric with all the statistics about how long reachability took
639
665
  *
666
+ * @param {boolean} aborted true if the reachability checks were aborted
640
667
  * @returns {void}
641
668
  */
642
- protected async sendMetric() {
669
+ protected async sendMetric(aborted = false) {
643
670
  const results = [];
644
671
 
645
672
  Object.values(this.clusterReachability).forEach((clusterReachability) => {
@@ -650,6 +677,7 @@ export default class Reachability extends EventsScope {
650
677
  });
651
678
 
652
679
  const stats = {
680
+ aborted,
653
681
  vmn: {
654
682
  udp: this.getStatistics(results, 'udp', true),
655
683
  },
@@ -450,6 +450,16 @@ describe('plugin-meetings', () => {
450
450
  assert.equal(SelfUtils.mutedByOthersChanged(null, {remoteMuted: true}), true);
451
451
  });
452
452
 
453
+ it('should return false when selfIdentity and modifiedBy are the same', function () {
454
+ assert.equal(
455
+ SelfUtils.mutedByOthersChanged(
456
+ {remoteMuted: false},
457
+ {remoteMuted: true, selfIdentity: 'user1', modifiedBy: 'user1'}
458
+ ),
459
+ false
460
+ );
461
+ });
462
+
453
463
  it('should return true when remoteMuted values are different', function () {
454
464
  assert.equal(
455
465
  SelfUtils.mutedByOthersChanged(
@@ -66,6 +66,21 @@ describe('MediaProperties', () => {
66
66
  assert.equal(numTransports, 0);
67
67
  });
68
68
 
69
+ it('handles time out in the case when getStats() is not resolving', async () => {
70
+ // Promise that never resolves
71
+ mockMC.getStats = new Promise(() => {});
72
+
73
+ const promise = mediaProperties.getCurrentConnectionInfo();
74
+
75
+ await clock.tickAsync(1000);
76
+
77
+ const {connectionType, selectedCandidatePairChanges, numTransports} = await promise;
78
+
79
+ assert.equal(connectionType, 'unknown');
80
+ assert.equal(selectedCandidatePairChanges, -1);
81
+ assert.equal(numTransports, 0);
82
+ });
83
+
69
84
  describe('selectedCandidatePairChanges and numTransports', () => {
70
85
  it('returns correct values when getStats() returns no transport stats at all', async () => {
71
86
  mockMC.getStats.resolves([{type: 'something', id: '1234'}]);
@@ -0,0 +1,114 @@
1
+ import sinon from 'sinon';
2
+ import {assert} from '@webex/test-helper-chai';
3
+
4
+ import testUtils from '../../../utils/testUtils';
5
+ import {BrbState, createBrbState} from '@webex/plugin-meetings/src/meeting/brbState';
6
+
7
+ describe('plugin-meetings', () => {
8
+ let meeting: any;
9
+ let brbState: BrbState;
10
+
11
+ beforeEach(async () => {
12
+ meeting = {
13
+ isMultistream: true,
14
+ locusUrl: 'locus url',
15
+ deviceUrl: 'device url',
16
+ selfId: 'self id',
17
+ mediaProperties: {
18
+ webrtcMediaConnection: true,
19
+ },
20
+ sendSlotManager: {
21
+ setSourceStateOverride: sinon.stub(),
22
+ },
23
+ meetingRequest: {
24
+ setBrb: sinon.stub().resolves(),
25
+ },
26
+ };
27
+
28
+ brbState = new BrbState(meeting, false);
29
+ await testUtils.flushPromises();
30
+ });
31
+
32
+ describe('brbState library', () => {
33
+ it('takes into account current status when instantiated', async () => {
34
+ // create a new BrbState instance
35
+ brbState = createBrbState(meeting, true);
36
+ await testUtils.flushPromises();
37
+
38
+ assert.isTrue(brbState.state.client.enabled);
39
+
40
+ // now check the opposite case
41
+ brbState = createBrbState(meeting, false);
42
+ await testUtils.flushPromises();
43
+
44
+ assert.isFalse(brbState.state.client.enabled);
45
+ });
46
+
47
+ it('can be enabled', async () => {
48
+ brbState.enable(true, meeting.sendSlotManager);
49
+ brbState.handleServerBrbUpdate(true);
50
+ await testUtils.flushPromises();
51
+
52
+ assert.isTrue(brbState.state.client.enabled);
53
+ assert.isTrue(brbState.state.server.enabled);
54
+ });
55
+
56
+ it('can be disabled', async () => {
57
+ brbState.enable(false, meeting.sendSlotManager);
58
+ brbState.handleServerBrbUpdate(false);
59
+ await testUtils.flushPromises();
60
+
61
+ assert.isFalse(brbState.state.client.enabled);
62
+ assert.isFalse(brbState.state.server.enabled);
63
+ });
64
+
65
+ it('does not send local brb state to server if it is not a multistream meeting', async () => {
66
+ meeting.isMultistream = false;
67
+ brbState.enable(true, meeting.sendSlotManager);
68
+ brbState.handleServerBrbUpdate(true);
69
+ await testUtils.flushPromises();
70
+
71
+ assert.isTrue(meeting.meetingRequest.setBrb.notCalled);
72
+ });
73
+
74
+ it('does not send local brb state to server if webrtc media connection is not defined', async () => {
75
+ meeting.mediaProperties.webrtcMediaConnection = undefined;
76
+ brbState.enable(true, meeting.sendSlotManager);
77
+ brbState.handleServerBrbUpdate(true);
78
+ await testUtils.flushPromises();
79
+
80
+ assert.isTrue(meeting.meetingRequest.setBrb.notCalled);
81
+ });
82
+
83
+ it('does not send request twice when in progress', async () => {
84
+ brbState.state.syncToServerInProgress = true;
85
+ brbState.enable(true, meeting.sendSlotManager);
86
+ await testUtils.flushPromises();
87
+
88
+ assert.isTrue(meeting.meetingRequest.setBrb.notCalled);
89
+ });
90
+
91
+ it('syncs with server when client state does not match server state', async () => {
92
+ brbState.enable(true, meeting.sendSlotManager);
93
+ brbState.handleServerBrbUpdate(true);
94
+ await testUtils.flushPromises();
95
+
96
+ assert.isTrue(meeting.meetingRequest.setBrb.calledOnce);
97
+ });
98
+
99
+ it('sets source state override when client state does not match server state', async () => {
100
+ brbState.enable(true, meeting.sendSlotManager);
101
+ brbState.handleServerBrbUpdate(true);
102
+ await testUtils.flushPromises();
103
+
104
+ assert.isTrue(meeting.sendSlotManager.setSourceStateOverride.calledOnce);
105
+ });
106
+
107
+ it('handles server update', async () => {
108
+ brbState.handleServerBrbUpdate(true);
109
+ await testUtils.flushPromises();
110
+
111
+ assert.isTrue(brbState.state.server.enabled);
112
+ });
113
+ });
114
+ });
@@ -114,6 +114,7 @@ import {ERROR_DESCRIPTIONS} from '@webex/internal-plugin-metrics/src/call-diagno
114
114
  import MeetingCollection from '@webex/plugin-meetings/src/meetings/collection';
115
115
 
116
116
  import {EVENT_TRIGGERS as VOICEAEVENTS} from '@webex/internal-plugin-voicea';
117
+ import { createBrbState } from '@webex/plugin-meetings/src/meeting/brbState';
117
118
  import JoinForbiddenError from '../../../../src/common/errors/join-forbidden-error';
118
119
 
119
120
  describe('plugin-meetings', () => {
@@ -246,6 +247,7 @@ describe('plugin-meetings', () => {
246
247
  isAnyPublicClusterReachable: sinon.stub().resolves(true),
247
248
  getReachabilityResults: sinon.stub().resolves(undefined),
248
249
  getReachabilityMetrics: sinon.stub().resolves({}),
250
+ stopReachability: sinon.stub(),
249
251
  };
250
252
  webex.internal.llm.on = sinon.stub();
251
253
  webex.internal.newMetrics.callDiagnosticLatencies = new CallDiagnosticLatencies(
@@ -2095,6 +2097,7 @@ describe('plugin-meetings', () => {
2095
2097
  someReachabilityMetric1: 'some value1',
2096
2098
  someReachabilityMetric2: 'some value2',
2097
2099
  }),
2100
+ stopReachability: sinon.stub(),
2098
2101
  };
2099
2102
 
2100
2103
  const forceRtcMetricsSend = sinon.stub().resolves();
@@ -2514,6 +2517,7 @@ describe('plugin-meetings', () => {
2514
2517
  assert.calledOnce(meeting.setMercuryListener);
2515
2518
  assert.calledOnce(fakeMediaConnection.initiateOffer);
2516
2519
  assert.equal(meeting.allowMediaInLobby, allowMediaInLobby);
2520
+ assert.calledOnce(webex.meetings.reachability.stopReachability);
2517
2521
  };
2518
2522
 
2519
2523
  it('should attach the media and return promise', async () => {
@@ -2709,6 +2713,7 @@ describe('plugin-meetings', () => {
2709
2713
  webex.meetings.reachability = {
2710
2714
  isWebexMediaBackendUnreachable: sinon.stub().resolves(false),
2711
2715
  getReachabilityMetrics: sinon.stub().resolves(),
2716
+ stopReachability: sinon.stub(),
2712
2717
  };
2713
2718
  const MOCK_CLIENT_ERROR_CODE = 2004;
2714
2719
  const generateClientErrorCodeForIceFailureStub = sinon
@@ -2917,6 +2922,7 @@ describe('plugin-meetings', () => {
2917
2922
  .onCall(2)
2918
2923
  .resolves(false),
2919
2924
  getReachabilityMetrics: sinon.stub().resolves({}),
2925
+ stopReachability: sinon.stub(),
2920
2926
  };
2921
2927
  const getErrorPayloadForClientErrorCodeStub =
2922
2928
  (webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode =
@@ -3211,6 +3217,7 @@ describe('plugin-meetings', () => {
3211
3217
  someReachabilityMetric1: 'some value1',
3212
3218
  someReachabilityMetric2: 'some value2',
3213
3219
  }),
3220
+ stopReachability: sinon.stub(),
3214
3221
  };
3215
3222
  meeting.iceCandidatesCount = 3;
3216
3223
  meeting.iceCandidateErrors.set('701_error', 3);
@@ -3715,6 +3722,7 @@ describe('plugin-meetings', () => {
3715
3722
 
3716
3723
  webex.meetings.reachability = {
3717
3724
  isWebexMediaBackendUnreachable: sinon.stub().resolves(unreachable || false),
3725
+ stopReachability: sinon.stub(),
3718
3726
  };
3719
3727
 
3720
3728
  const generateClientErrorCodeForIceFailureStub = sinon
@@ -3812,7 +3820,6 @@ describe('plugin-meetings', () => {
3812
3820
  };
3813
3821
 
3814
3822
  beforeEach(() => {
3815
- meeting.meetingRequest.setBrb = sinon.stub().resolves({body: 'test'});
3816
3823
  meeting.mediaProperties.webrtcMediaConnection = {createSendSlot: sinon.stub()};
3817
3824
  meeting.sendSlotManager.createSlot(
3818
3825
  fakeMultistreamRoapMediaConnection,
@@ -3822,6 +3829,8 @@ describe('plugin-meetings', () => {
3822
3829
  meeting.locusUrl = 'locus url';
3823
3830
  meeting.deviceUrl = 'device url';
3824
3831
  meeting.selfId = 'self id';
3832
+ meeting.brbState = createBrbState(meeting, false);
3833
+ meeting.brbState.enable = sinon.stub().resolves();
3825
3834
  });
3826
3835
 
3827
3836
  afterEach(() => {
@@ -3843,7 +3852,7 @@ describe('plugin-meetings', () => {
3843
3852
 
3844
3853
  await brbResult;
3845
3854
  assert.exists(brbResult.then);
3846
- assert.calledOnce(meeting.meetingRequest.setBrb);
3855
+ assert.calledOnce(meeting.brbState.enable);
3847
3856
  })
3848
3857
 
3849
3858
  it('should disable #beRightBack and return a promise', async () => {
@@ -3851,12 +3860,12 @@ describe('plugin-meetings', () => {
3851
3860
 
3852
3861
  await brbResult;
3853
3862
  assert.exists(brbResult.then);
3854
- assert.calledOnce(meeting.meetingRequest.setBrb);
3863
+ assert.calledOnce(meeting.brbState.enable);
3855
3864
  })
3856
3865
 
3857
3866
  it('should throw an error and reject the promise if setBrb fails', async () => {
3858
3867
  const error = new Error('setBrb failed');
3859
- meeting.meetingRequest.setBrb.rejects(error);
3868
+ meeting.brbState.enable.rejects(error);
3860
3869
 
3861
3870
  try {
3862
3871
  await meeting.beRightBack(true);
@@ -3867,27 +3876,6 @@ describe('plugin-meetings', () => {
3867
3876
  }
3868
3877
  })
3869
3878
  });
3870
-
3871
- describe('when in a transcoded meeting', () => {
3872
-
3873
- beforeEach(() => {
3874
- meeting.isMultistream = false;
3875
- });
3876
-
3877
- it('should ignore enabling #beRightBack', async () => {
3878
- meeting.beRightBack(true);
3879
-
3880
- assert.isRejected((Promise.reject()));
3881
- assert.notCalled(meeting.meetingRequest.setBrb);
3882
- })
3883
-
3884
- it('should ignore disabling #beRightBack', async () => {
3885
- meeting.beRightBack(false);
3886
-
3887
- assert.isRejected((Promise.reject()));
3888
- assert.notCalled(meeting.meetingRequest.setBrb);
3889
- })
3890
- });
3891
3879
  });
3892
3880
 
3893
3881
  /* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
@@ -6804,7 +6792,7 @@ describe('plugin-meetings', () => {
6804
6792
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
6805
6793
  assert.equal(
6806
6794
  meeting.meetingInfoFailureReason,
6807
- MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID
6795
+ MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATION_ID
6808
6796
  );
6809
6797
  });
6810
6798
  });
@@ -6864,7 +6852,8 @@ describe('plugin-meetings', () => {
6864
6852
  'fake-installed-org-id',
6865
6853
  'locus-id',
6866
6854
  {extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
6867
- {meetingId: meeting.id, sendCAevents: true}
6855
+ {meetingId: meeting.id, sendCAevents: true},
6856
+ null
6868
6857
  );
6869
6858
  assert.deepEqual(meeting.meetingInfo, {
6870
6859
  ...FAKE_MEETING_INFO,
@@ -6909,7 +6898,8 @@ describe('plugin-meetings', () => {
6909
6898
  'fake-installed-org-id',
6910
6899
  'locus-id',
6911
6900
  {extraParam1: 'value1', permissionToken: FAKE_PERMISSION_TOKEN},
6912
- {meetingId: meeting.id, sendCAevents: true}
6901
+ {meetingId: meeting.id, sendCAevents: true},
6902
+ null
6913
6903
  );
6914
6904
  assert.deepEqual(meeting.meetingInfo, {
6915
6905
  ...FAKE_MEETING_INFO,
@@ -6963,7 +6953,8 @@ describe('plugin-meetings', () => {
6963
6953
  extraParam1: 'value1',
6964
6954
  permissionToken: FAKE_PERMISSION_TOKEN,
6965
6955
  },
6966
- {meetingId: meeting.id, sendCAevents: true}
6956
+ {meetingId: meeting.id, sendCAevents: true},
6957
+ null
6967
6958
  );
6968
6959
  assert.deepEqual(meeting.meetingInfo, {
6969
6960
  ...FAKE_MEETING_INFO,
@@ -9230,6 +9221,7 @@ describe('plugin-meetings', () => {
9230
9221
 
9231
9222
  it('listens to the brb state changed event', () => {
9232
9223
  const assertBrb = (enabled) => {
9224
+ meeting.brbState = createBrbState(meeting, false);
9233
9225
  meeting.locusInfo.emit(
9234
9226
  { function: 'test', file: 'test' },
9235
9227
  LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED,
@@ -11259,7 +11251,10 @@ describe('plugin-meetings', () => {
11259
11251
 
11260
11252
  const result = await meeting.updateLLMConnection();
11261
11253
 
11262
- assert.calledWith(webex.internal.llm.disconnectLLM);
11254
+ assert.calledWith(webex.internal.llm.disconnectLLM, {
11255
+ code: 3050,
11256
+ reason: 'done (permanent)',
11257
+ });
11263
11258
  assert.calledWith(
11264
11259
  webex.internal.llm.registerAndConnect,
11265
11260
  'a different url',
@@ -11289,7 +11284,10 @@ describe('plugin-meetings', () => {
11289
11284
 
11290
11285
  const result = await meeting.updateLLMConnection();
11291
11286
 
11292
- assert.calledWith(webex.internal.llm.disconnectLLM);
11287
+ assert.calledWith(webex.internal.llm.disconnectLLM, {
11288
+ code: 3050,
11289
+ reason: 'done (permanent)',
11290
+ });
11293
11291
  assert.calledWith(
11294
11292
  webex.internal.llm.registerAndConnect,
11295
11293
  'a url',
@@ -11318,7 +11316,7 @@ describe('plugin-meetings', () => {
11318
11316
 
11319
11317
  const result = await meeting.updateLLMConnection();
11320
11318
 
11321
- assert.calledWith(webex.internal.llm.disconnectLLM);
11319
+ assert.calledWith(webex.internal.llm.disconnectLLM, undefined);
11322
11320
  assert.notCalled(webex.internal.llm.registerAndConnect);
11323
11321
  assert.equal(result, undefined);
11324
11322
  assert.calledOnceWithExactly(
@@ -13260,7 +13258,7 @@ describe('plugin-meetings', () => {
13260
13258
 
13261
13259
  describe('#roapMessageReceived', () => {
13262
13260
  it('calls roapMessageReceived on the webrtc media connection', () => {
13263
- const fakeMessage = {messageType: 'fake', sdp: 'fake sdp'};
13261
+ const fakeMessage = {messageType: 'ANSWER', sdp: 'fake sdp'};
13264
13262
 
13265
13263
  const getMediaServer = sinon.stub(MeetingsUtil, 'getMediaServer').returns('homer');
13266
13264
 
@@ -13298,5 +13296,75 @@ describe('plugin-meetings', () => {
13298
13296
 
13299
13297
  assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived);
13300
13298
  });
13299
+
13300
+ it('does not call getMediaServer for a roap message other than ANSWER', async () => {
13301
+ const fakeMessage = {messageType: 'ERROR', sdp: 'fake sdp'};
13302
+
13303
+ meeting.isMultistream = true;
13304
+ meeting.mediaProperties.webrtcMediaConnection = {
13305
+ roapMessageReceived: sinon.stub(),
13306
+ };
13307
+ meeting.mediaProperties.webrtcMediaConnection.mediaServer = 'linus';
13308
+
13309
+ const getMediaServerStub = sinon.stub(MeetingsUtil, 'getMediaServer').returns('something');
13310
+
13311
+ meeting.roapMessageReceived(fakeMessage);
13312
+
13313
+ assert.calledOnceWithExactly(
13314
+ meeting.mediaProperties.webrtcMediaConnection.roapMessageReceived,
13315
+ fakeMessage
13316
+ );
13317
+ assert.notCalled(getMediaServerStub);
13318
+ assert.equal(meeting.mediaProperties.webrtcMediaConnection.mediaServer, 'linus'); // check that it hasn't been overwritten
13319
+ });
13320
+ });
13321
+
13322
+ describe('#verifyRegistrationId', () => {
13323
+ it('calls fetchMeetingInfo() with the passed registrationId and captcha code', async () => {
13324
+ // simulate successful case
13325
+ meeting.fetchMeetingInfo = sinon.stub().resolves();
13326
+ const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
13327
+
13328
+ assert(Metrics.sendBehavioralMetric.calledOnce);
13329
+ assert.calledWith(
13330
+ Metrics.sendBehavioralMetric,
13331
+ BEHAVIORAL_METRICS.VERIFY_REGISTRATION_ID_SUCCESS
13332
+ );
13333
+ assert.equal(result.isRegistrationIdValid, true);
13334
+ assert.equal(result.requiredCaptcha, null);
13335
+ assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.NONE);
13336
+ assert.calledWith(meeting.fetchMeetingInfo, {
13337
+ registrationId: 'registrationId',
13338
+ captchaCode: 'captcha id',
13339
+ sendCAevents: false,
13340
+ });
13341
+ });
13342
+ it('handles registrationIdError returned by fetchMeetingInfo', async () => {
13343
+ meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
13344
+ meeting.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATIONID;
13345
+
13346
+ return Promise.reject(new JoinWebinarError());
13347
+ });
13348
+ const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
13349
+
13350
+ assert.equal(result.isRegistrationIdValid, false);
13351
+ assert.equal(result.requiredCaptcha, null);
13352
+ assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATION_ID);
13353
+ });
13354
+ it('handles CaptchaError returned by fetchMeetingInfo', async () => {
13355
+ const FAKE_CAPTCHA = {captchaId: 'some catcha id...'};
13356
+
13357
+ meeting.fetchMeetingInfo = sinon.stub().callsFake(() => {
13358
+ meeting.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA;
13359
+ meeting.requiredCaptcha = FAKE_CAPTCHA;
13360
+
13361
+ return Promise.reject(new CaptchaError());
13362
+ });
13363
+ const result = await meeting.verifyRegistrationId('registrationId', 'captcha id');
13364
+
13365
+ assert.equal(result.isRegistrationIdValid, false);
13366
+ assert.deepEqual(result.requiredCaptcha, FAKE_CAPTCHA);
13367
+ assert.equal(result.failureReason, MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA);
13368
+ });
13301
13369
  });
13302
13370
  });
@@ -113,30 +113,6 @@ describe('plugin-meetings', () => {
113
113
  assert.isTrue(audio.isRemotelyMuted());
114
114
  });
115
115
 
116
- it('does not locally unmute on a server unmute', async () => {
117
- const setServerMutedSpy = meeting.mediaProperties.audioStream.setServerMuted;
118
-
119
- // simulate remote mute
120
- audio.handleServerRemoteMuteUpdate(meeting, true, true);
121
-
122
- assert.isTrue(audio.isRemotelyMuted());
123
- assert.isTrue(audio.isLocallyMuted());
124
-
125
- // mutes local
126
- assert.calledOnceWithExactly(setServerMutedSpy, true, 'remotelyMuted');
127
-
128
- setServerMutedSpy.resetHistory();
129
-
130
- // simulate remote unmute
131
- audio.handleServerRemoteMuteUpdate(meeting, false, true);
132
-
133
- assert.isFalse(audio.isRemotelyMuted());
134
- assert.isTrue(audio.isLocallyMuted());
135
-
136
- // does not unmute local
137
- assert.notCalled(setServerMutedSpy);
138
- });
139
-
140
116
  it('does local audio unmute if localAudioUnmuteRequired is received', async () => {
141
117
  // first we need to have the local stream user muted
142
118
  meeting.mediaProperties.audioStream.userMuted = true;
@@ -192,6 +192,15 @@ describe('plugin-meetings', () => {
192
192
  assert.equal(res.meetingUUID, 'xsddsdsdsdssdsdsdsdsd');
193
193
  });
194
194
 
195
+ it('for registrationId', () => {
196
+ const res = MeetingInfoUtil.getRequestBody({
197
+ type: DESTINATION_TYPE.MEETING_UUID,
198
+ registrationId: 'registrationId',
199
+ });
200
+
201
+ assert.equal(res.registrationId, 'registrationId');
202
+ });
203
+
195
204
  it('for DESTINATION_TYPE.LOCUS_ID', () => {
196
205
  const res = MeetingInfoUtil.getRequestBody({
197
206
  type: DESTINATION_TYPE.LOCUS_ID,