@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
package/package.json CHANGED
@@ -43,13 +43,13 @@
43
43
  "@webex/eslint-config-legacy": "0.0.0",
44
44
  "@webex/jest-config-legacy": "0.0.0",
45
45
  "@webex/legacy-tools": "0.0.0",
46
- "@webex/plugin-meetings": "3.7.0-wxcc.1",
47
- "@webex/plugin-rooms": "3.7.0-wxcc.1",
48
- "@webex/test-helper-chai": "3.7.0-wxcc.1",
49
- "@webex/test-helper-mocha": "3.7.0-wxcc.1",
50
- "@webex/test-helper-mock-webex": "3.7.0-wxcc.1",
51
- "@webex/test-helper-retry": "3.7.0-wxcc.1",
52
- "@webex/test-helper-test-users": "3.7.0-wxcc.1",
46
+ "@webex/plugin-meetings": "3.8.0-next.1",
47
+ "@webex/plugin-rooms": "3.7.0-next.25",
48
+ "@webex/test-helper-chai": "3.7.0-next.18",
49
+ "@webex/test-helper-mocha": "3.7.0-next.18",
50
+ "@webex/test-helper-mock-webex": "3.7.0-next.18",
51
+ "@webex/test-helper-retry": "3.7.0-next.18",
52
+ "@webex/test-helper-test-users": "3.7.0-next.18",
53
53
  "chai": "^4.3.4",
54
54
  "chai-as-promised": "^7.1.1",
55
55
  "eslint": "^8.24.0",
@@ -61,22 +61,22 @@
61
61
  "typescript": "^4.7.4"
62
62
  },
63
63
  "dependencies": {
64
- "@webex/common": "3.7.0-wxcc.1",
65
- "@webex/event-dictionary-ts": "^1.0.1643",
66
- "@webex/internal-media-core": "2.14.2",
67
- "@webex/internal-plugin-conversation": "3.7.0-wxcc.1",
68
- "@webex/internal-plugin-device": "3.7.0-wxcc.1",
69
- "@webex/internal-plugin-llm": "3.7.0-wxcc.1",
70
- "@webex/internal-plugin-mercury": "3.7.0-wxcc.1",
71
- "@webex/internal-plugin-metrics": "3.7.0-wxcc.1",
72
- "@webex/internal-plugin-support": "3.7.0-wxcc.1",
73
- "@webex/internal-plugin-user": "3.7.0-wxcc.1",
74
- "@webex/internal-plugin-voicea": "3.7.0-wxcc.1",
75
- "@webex/media-helpers": "3.7.0-wxcc.1",
76
- "@webex/plugin-people": "3.7.0-wxcc.1",
77
- "@webex/plugin-rooms": "3.7.0-wxcc.1",
64
+ "@webex/common": "3.7.0-next.18",
65
+ "@webex/event-dictionary-ts": "^1.0.1688",
66
+ "@webex/internal-media-core": "2.14.4",
67
+ "@webex/internal-plugin-conversation": "3.7.0-next.25",
68
+ "@webex/internal-plugin-device": "3.7.0-next.18",
69
+ "@webex/internal-plugin-llm": "3.8.0-next.1",
70
+ "@webex/internal-plugin-mercury": "3.7.0-next.23",
71
+ "@webex/internal-plugin-metrics": "3.7.0-next.18",
72
+ "@webex/internal-plugin-support": "3.7.0-next.26",
73
+ "@webex/internal-plugin-user": "3.7.0-next.18",
74
+ "@webex/internal-plugin-voicea": "3.8.0-next.1",
75
+ "@webex/media-helpers": "3.7.0-next.25",
76
+ "@webex/plugin-people": "3.7.0-next.23",
77
+ "@webex/plugin-rooms": "3.7.0-next.25",
78
78
  "@webex/web-capabilities": "^1.4.0",
79
- "@webex/webex-core": "3.7.0-wxcc.1",
79
+ "@webex/webex-core": "3.7.0-next.18",
80
80
  "ampersand-collection": "^2.0.2",
81
81
  "bowser": "^2.11.0",
82
82
  "btoa": "^1.2.1",
@@ -92,5 +92,5 @@
92
92
  "//": [
93
93
  "TODO: upgrade jwt-decode when moving to node 18"
94
94
  ],
95
- "version": "3.7.0-wxcc.1"
95
+ "version": "3.8.0-next.1"
96
96
  }
package/src/constants.ts CHANGED
@@ -202,7 +202,7 @@ export const RETRY_TIMEOUT = 3000;
202
202
  export const ICE_AND_DTLS_CONNECTION_TIMEOUT = 20000;
203
203
  export const ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT = 35000;
204
204
  export const WEBINAR_ERROR_WEBCAST = [403026];
205
- export const WEBINAR_ERROR_REGISTRATIONID = [403037, 403137];
205
+ export const WEBINAR_ERROR_REGISTRATION_ID = [403037, 403137];
206
206
  export const JOIN_BEFORE_HOST = 403003;
207
207
 
208
208
  // ******************** REGEX **********************
@@ -1331,14 +1331,22 @@ export const PASSWORD_STATUS = {
1331
1331
  VERIFIED: 'VERIFIED', // client has already provided the password and it has been verified, client can proceed to call join()
1332
1332
  };
1333
1333
 
1334
+ export const REGISTRATION_ID_STATUS = {
1335
+ NOT_REQUIRED: 'NOT_REQUIRED', // registrationId is not required to join the meeting
1336
+ REQUIRED: 'REQUIRED', // client needs to provide the registrationId by calling verifyRegistrationId() before calling join()
1337
+ UNKNOWN: 'UNKNOWN', // we are waiting for information from the backend if registrationId is required or not
1338
+ VERIFIED: 'VERIFIED', // client has already provided the registrationId and it has been verified, client can proceed to call join()
1339
+ };
1340
+
1334
1341
  export const MEETING_INFO_FAILURE_REASON = {
1335
1342
  NONE: 'NONE', // meeting info was retrieved succesfully
1336
1343
  WRONG_PASSWORD: 'WRONG_PASSWORD', // meeting requires password and no password or wrong one was provided
1337
1344
  WRONG_CAPTCHA: 'WRONG_CAPTCHA', // wbxappapi requires a captcha code or a wrong captcha code was provided
1345
+ WRONG_REGISTRATION_ID: 'WRONG_REGISTRATION_ID', // meeting requires registrationId and no registrationId or wrong one was provided
1338
1346
  POLICY: 'POLICY', // meeting info request violates some meeting policy
1339
1347
  WEBINAR_REGISTRATION: 'WEBINAR_REGISTRATION', // webinar need registration
1340
1348
  NEED_JOIN_WITH_WEBCAST: 'NEED_JOIN_WITH_WEBCAST', // webinar need using webcast join
1341
- WEBINAR_NEED_REGISTRATIONID: 'WEBINAR_NEED_REGISTRATIONID', // webinar need registrationID
1349
+ WEBINAR_NEED_REGISTRATION_ID: 'WEBINAR_NEED_REGISTRATION_ID', // webinar need registrationID
1342
1350
  NOT_REACH_JBH: 'NOT_REACH_JBH', // Meeting is not allow to access since not reach JBH (join before host) time
1343
1351
  JOIN_FORBIDDEN: 'JOIN_FORBIDDEN', // meeting is not allow join
1344
1352
  OTHER: 'OTHER', // any other error (network, etc)
@@ -441,6 +441,11 @@ SelfUtils.mutedByOthersChanged = (oldSelf, changedSelf) => {
441
441
  return false;
442
442
  }
443
443
 
444
+ // there is no need to trigger user update if no one muted user
445
+ if (changedSelf.selfIdentity === changedSelf.modifiedBy) {
446
+ return false;
447
+ }
448
+
444
449
  return (
445
450
  changedSelf.remoteMuted !== null &&
446
451
  (oldSelf.remoteMuted !== changedSelf.remoteMuted ||
@@ -111,6 +111,8 @@ export default class MediaConnectionAwaiter {
111
111
 
112
112
  this.clearCallbacks();
113
113
 
114
+ LoggerProxy.logger.warn('Media:MediaConnectionAwaiter#connectionStateChange --> Resolving');
115
+
114
116
  this.defer.resolve();
115
117
  }
116
118
 
@@ -287,24 +287,45 @@ export default class MediaProperties {
287
287
  selectedCandidatePairChanges: number;
288
288
  numTransports: number;
289
289
  }> {
290
- const allStatsReports = [];
291
-
292
290
  try {
293
- const statsResult = await this.webrtcMediaConnection.getStats();
294
- statsResult.forEach((report) => allStatsReports.push(report));
291
+ const allStatsReports = [];
292
+
293
+ await new Promise((resolve, reject) => {
294
+ const timeout = setTimeout(() => {
295
+ reject(new Error('timed out'));
296
+ }, 1000);
297
+
298
+ this.webrtcMediaConnection
299
+ .getStats()
300
+ .then((statsResult) => {
301
+ clearTimeout(timeout);
302
+ statsResult.forEach((report) => allStatsReports.push(report));
303
+ resolve(allStatsReports);
304
+ })
305
+ .catch((error) => {
306
+ clearTimeout(timeout);
307
+ reject(error);
308
+ });
309
+ });
310
+
311
+ const connectionType = this.getConnectionType(allStatsReports);
312
+ const {selectedCandidatePairChanges, numTransports} = this.getTransportInfo(allStatsReports);
313
+
314
+ return {
315
+ connectionType,
316
+ selectedCandidatePairChanges,
317
+ numTransports,
318
+ };
295
319
  } catch (error) {
296
320
  LoggerProxy.logger.warn(
297
321
  `Media:properties#getCurrentConnectionInfo --> getStats() failed: ${error}`
298
322
  );
299
- }
300
-
301
- const connectionType = this.getConnectionType(allStatsReports);
302
- const {selectedCandidatePairChanges, numTransports} = this.getTransportInfo(allStatsReports);
303
323
 
304
- return {
305
- connectionType,
306
- selectedCandidatePairChanges,
307
- numTransports,
308
- };
324
+ return {
325
+ connectionType: 'unknown',
326
+ selectedCandidatePairChanges: -1,
327
+ numTransports: 0,
328
+ };
329
+ }
309
330
  }
310
331
  }
@@ -0,0 +1,169 @@
1
+ import {MediaType} from '@webex/internal-media-core';
2
+ import LoggerProxy from '../common/logs/logger-proxy';
3
+ import type Meeting from '.';
4
+ import SendSlotManager from '../multistream/sendSlotManager';
5
+
6
+ export const createBrbState = (meeting: Meeting, enabled: boolean) => {
7
+ LoggerProxy.logger.info(
8
+ `Meeting:brbState#createBrbState: creating BrbState for meeting id ${meeting?.id}`
9
+ );
10
+
11
+ const brbState = new BrbState(meeting, enabled);
12
+
13
+ return brbState;
14
+ };
15
+
16
+ /** The purpose of this class is to manage the local and remote brb state
17
+ * and make sure that the server state always matches the last requested state by the client.
18
+ */
19
+ export class BrbState {
20
+ state: {
21
+ client: {
22
+ enabled: boolean;
23
+ };
24
+ server: {
25
+ enabled: boolean;
26
+ };
27
+ syncToServerInProgress: boolean;
28
+ };
29
+
30
+ meeting: Meeting;
31
+
32
+ /**
33
+ * Constructor
34
+ *
35
+ * @param {Meeting} meeting - the meeting object
36
+ * @param {boolean} enabled - whether the client audio/video is enabled at all
37
+ */
38
+ constructor(meeting: Meeting, enabled: boolean) {
39
+ this.meeting = meeting;
40
+ this.state = {
41
+ client: {
42
+ enabled,
43
+ },
44
+ server: {
45
+ enabled: false,
46
+ },
47
+ syncToServerInProgress: false,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Enables/disables brb
53
+ *
54
+ * @param {boolean} enabled
55
+ * @param {SendSlotManager} sendSlotManager
56
+ * @returns {Promise}
57
+ */
58
+ public enable(enabled: boolean, sendSlotManager: SendSlotManager) {
59
+ this.state.client.enabled = enabled;
60
+
61
+ return this.applyClientStateToServer(sendSlotManager);
62
+ }
63
+
64
+ /**
65
+ * Updates the server local and remote brb values so that they match the current client desired state.
66
+ *
67
+ * @param {SendSlotManager} sendSlotManager
68
+ * @returns {Promise}
69
+ */
70
+ private applyClientStateToServer(sendSlotManager: SendSlotManager) {
71
+ if (this.state.syncToServerInProgress) {
72
+ LoggerProxy.logger.info(
73
+ `Meeting:brbState#applyClientStateToServer: request to server in progress, we need to wait for it to complete`
74
+ );
75
+
76
+ return Promise.resolve();
77
+ }
78
+
79
+ const remoteBrbRequiresSync = this.state.client.enabled !== this.state.server.enabled;
80
+
81
+ LoggerProxy.logger.info(
82
+ `Meeting:brbState#applyClientStateToServer: remoteBrbRequiresSync: ${remoteBrbRequiresSync}`
83
+ );
84
+
85
+ if (!remoteBrbRequiresSync) {
86
+ LoggerProxy.logger.info(
87
+ `Meeting:brbState#applyClientStateToServer: client state already matching server state, nothing to do`
88
+ );
89
+
90
+ return Promise.resolve();
91
+ }
92
+
93
+ this.state.syncToServerInProgress = true;
94
+
95
+ return this.sendLocalBrbStateToServer(sendSlotManager)
96
+ .then(() => {
97
+ this.state.syncToServerInProgress = false;
98
+ LoggerProxy.logger.info(
99
+ `Meeting:brbState#applyClientStateToServer: sync with server completed`
100
+ );
101
+
102
+ // need to check if a new sync is required, because this.state.client may have changed while we were doing the current sync
103
+ this.applyClientStateToServer(sendSlotManager);
104
+ })
105
+ .catch((e) => {
106
+ this.state.syncToServerInProgress = false;
107
+ LoggerProxy.logger.warn(`Meeting:brbState#applyClientStateToServer: error: ${e}`);
108
+ });
109
+ }
110
+
111
+ /**
112
+ * Send the local brb state to the server
113
+ *
114
+ * @param {SendSlotManager} sendSlotManager
115
+ * @returns {Promise}
116
+ */
117
+ private async sendLocalBrbStateToServer(sendSlotManager: SendSlotManager) {
118
+ const {enabled} = this.state.client;
119
+
120
+ if (!this.meeting.isMultistream) {
121
+ const errorMessage = 'Meeting:brbState#sendLocalBrbStateToServer: Not a multistream meeting';
122
+ const error = new Error(errorMessage);
123
+
124
+ LoggerProxy.logger.error(error);
125
+
126
+ return Promise.reject(error);
127
+ }
128
+
129
+ if (!this.meeting.mediaProperties.webrtcMediaConnection) {
130
+ const errorMessage =
131
+ 'Meeting:brbState#sendLocalBrbStateToServer: WebRTC media connection is not defined';
132
+ const error = new Error(errorMessage);
133
+
134
+ LoggerProxy.logger.error(error);
135
+
136
+ return Promise.reject(error);
137
+ }
138
+
139
+ // this logic should be applied only to multistream meetings
140
+ return this.meeting.meetingRequest
141
+ .setBrb({
142
+ enabled,
143
+ locusUrl: this.meeting.locusUrl,
144
+ deviceUrl: this.meeting.deviceUrl,
145
+ selfId: this.meeting.selfId,
146
+ })
147
+ .then(() => {
148
+ sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
149
+ })
150
+ .catch((error) => {
151
+ LoggerProxy.logger.error('Meeting:brbState#sendLocalBrbStateToServer: Error ', error);
152
+
153
+ return Promise.reject(error);
154
+ });
155
+ }
156
+
157
+ /**
158
+ * This method should be called whenever the server brb state is changed
159
+ *
160
+ * @param {Boolean} [enabled] true if user has brb enabled, false otherwise
161
+ * @returns {undefined}
162
+ */
163
+ public handleServerBrbUpdate(enabled?: boolean) {
164
+ LoggerProxy.logger.info(
165
+ `Meeting:brbState#handleServerBrbUpdate: updating server brb to (${enabled})`
166
+ );
167
+ this.state.server.enabled = !!enabled;
168
+ }
169
+ }
@@ -122,8 +122,9 @@ import {
122
122
  ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
123
123
  NAMED_MEDIA_GROUP_TYPE_AUDIO,
124
124
  WEBINAR_ERROR_WEBCAST,
125
- WEBINAR_ERROR_REGISTRATIONID,
125
+ WEBINAR_ERROR_REGISTRATION_ID,
126
126
  JOIN_BEFORE_HOST,
127
+ REGISTRATION_ID_STATUS,
127
128
  } from '../constants';
128
129
  import BEHAVIORAL_METRICS from '../metrics/constants';
129
130
  import ParameterError from '../common/errors/parameter';
@@ -163,6 +164,7 @@ import {LocusMediaRequest} from './locusMediaRequest';
163
164
  import {ConnectionStateHandler, ConnectionStateEvent} from './connectionStateHandler';
164
165
  import JoinWebinarError from '../common/errors/join-webinar-error';
165
166
  import Member from '../member';
167
+ import {BrbState, createBrbState} from './brbState';
166
168
  import MultistreamNotSupportedError from '../common/errors/multistream-not-supported-error';
167
169
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
168
170
 
@@ -255,6 +257,7 @@ export enum ScreenShareFloorStatus {
255
257
 
256
258
  type FetchMeetingInfoParams = {
257
259
  password?: string;
260
+ registrationId?: string;
258
261
  captchaCode?: string;
259
262
  extraParams?: Record<string, any>;
260
263
  sendCAevents?: boolean;
@@ -649,6 +652,8 @@ export default class Meeting extends StatelessWebexPlugin {
649
652
  turnServerUsed: boolean;
650
653
  areVoiceaEventsSetup = false;
651
654
  isMoveToInProgress = false;
655
+ registrationIdStatus: string;
656
+ brbState: BrbState;
652
657
 
653
658
  voiceaListenerCallbacks: object = {
654
659
  [VOICEAEVENTS.VOICEA_ANNOUNCEMENT]: (payload: Transcription['languageOptions']) => {
@@ -1345,6 +1350,16 @@ export default class Meeting extends StatelessWebexPlugin {
1345
1350
  */
1346
1351
  this.passwordStatus = PASSWORD_STATUS.UNKNOWN;
1347
1352
 
1353
+ /**
1354
+ * registrationId status. If it's REGISTRATIONID_STATUS.REQUIRED then verifyRegistrationId() needs to be called
1355
+ * with the correct registrationId before calling join()
1356
+ * @instance
1357
+ * @type {REGISTRATION_ID_STATUS}
1358
+ * @public
1359
+ * @memberof Meeting
1360
+ */
1361
+ this.registrationIdStatus = REGISTRATION_ID_STATUS.UNKNOWN;
1362
+
1348
1363
  /**
1349
1364
  * Information about required captcha. If null, then no captcha is required. status. If it's PASSWORD_STATUS.REQUIRED then verifyPassword() needs to be called
1350
1365
  * with the correct password before calling join()
@@ -1657,6 +1672,15 @@ export default class Meeting extends StatelessWebexPlugin {
1657
1672
  this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
1658
1673
  }
1659
1674
 
1675
+ if (
1676
+ this.registrationIdStatus === REGISTRATION_ID_STATUS.REQUIRED ||
1677
+ this.registrationIdStatus === REGISTRATION_ID_STATUS.VERIFIED
1678
+ ) {
1679
+ this.registrationIdStatus = REGISTRATION_ID_STATUS.VERIFIED;
1680
+ } else {
1681
+ this.registrationIdStatus = REGISTRATION_ID_STATUS.NOT_REQUIRED;
1682
+ }
1683
+
1660
1684
  Trigger.trigger(
1661
1685
  this,
1662
1686
  {
@@ -1700,7 +1724,12 @@ export default class Meeting extends StatelessWebexPlugin {
1700
1724
  * @private
1701
1725
  */
1702
1726
  private prepForFetchMeetingInfo(
1703
- {password = null, captchaCode = null, extraParams = {}}: FetchMeetingInfoParams,
1727
+ {
1728
+ password = null,
1729
+ registrationId = null,
1730
+ captchaCode = null,
1731
+ extraParams = {},
1732
+ }: FetchMeetingInfoParams,
1704
1733
  caller: string
1705
1734
  ): Promise<void> {
1706
1735
  // when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
@@ -1740,6 +1769,7 @@ export default class Meeting extends StatelessWebexPlugin {
1740
1769
  captchaCode = null,
1741
1770
  extraParams = {},
1742
1771
  sendCAevents = false,
1772
+ registrationId = null,
1743
1773
  }): Promise<void> {
1744
1774
  try {
1745
1775
  const captchaInfo = captchaCode
@@ -1755,7 +1785,8 @@ export default class Meeting extends StatelessWebexPlugin {
1755
1785
  this.config.installedOrgID,
1756
1786
  this.locusId,
1757
1787
  extraParams,
1758
- {meetingId: this.id, sendCAevents}
1788
+ {meetingId: this.id, sendCAevents},
1789
+ registrationId
1759
1790
  );
1760
1791
 
1761
1792
  this.parseMeetingInfo(info?.body, this.destination, info?.errors);
@@ -1777,14 +1808,15 @@ export default class Meeting extends StatelessWebexPlugin {
1777
1808
  this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_REGISTRATION;
1778
1809
  if (WEBINAR_ERROR_WEBCAST.includes(err.wbxAppApiCode)) {
1779
1810
  this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NEED_JOIN_WITH_WEBCAST;
1780
- } else if (WEBINAR_ERROR_REGISTRATIONID.includes(err.wbxAppApiCode)) {
1781
- this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATIONID;
1811
+ } else if (WEBINAR_ERROR_REGISTRATION_ID.includes(err.wbxAppApiCode)) {
1812
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WEBINAR_NEED_REGISTRATION_ID;
1782
1813
  }
1783
1814
  this.meetingInfoFailureCode = err.wbxAppApiCode;
1784
1815
 
1785
1816
  if (err.meetingInfo) {
1786
1817
  this.meetingInfo = err.meetingInfo;
1787
1818
  }
1819
+ this.requiredCaptcha = null;
1788
1820
 
1789
1821
  throw new JoinWebinarError();
1790
1822
  } else if (err instanceof MeetingInfoV2JoinForbiddenError) {
@@ -1829,9 +1861,13 @@ export default class Meeting extends StatelessWebexPlugin {
1829
1861
  `Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - captcha required (code=${err?.body?.code}).`
1830
1862
  );
1831
1863
 
1832
- this.meetingInfoFailureReason = this.requiredCaptcha
1833
- ? MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA
1834
- : MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
1864
+ if (this.requiredCaptcha) {
1865
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA;
1866
+ } else if (err.isRegistrationIdRequired) {
1867
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATION_ID;
1868
+ } else {
1869
+ this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
1870
+ }
1835
1871
 
1836
1872
  this.meetingInfoFailureCode = err.wbxAppApiCode;
1837
1873
 
@@ -1839,6 +1875,10 @@ export default class Meeting extends StatelessWebexPlugin {
1839
1875
  this.passwordStatus = PASSWORD_STATUS.REQUIRED;
1840
1876
  }
1841
1877
 
1878
+ if (err.isRegistrationIdRequired) {
1879
+ this.registrationIdStatus = REGISTRATION_ID_STATUS.REQUIRED;
1880
+ }
1881
+
1842
1882
  this.requiredCaptcha = err.captchaInfo;
1843
1883
  throw new CaptchaError();
1844
1884
  } else {
@@ -1979,6 +2019,48 @@ export default class Meeting extends StatelessWebexPlugin {
1979
2019
  });
1980
2020
  }
1981
2021
 
2022
+ /**
2023
+ * Checks if the supplied registrationId is correct. It returns a promise with information whether the
2024
+ * registrationId and captcha code were correct or not.
2025
+ * @param {String | undefined} registrationId - can be undefined if only captcha was required
2026
+ * @param {String | undefined} captchaCode - can be undefined if captcha was not required by the server
2027
+ * @param {Boolean} sendCAevents - whether Call Analyzer events should be sent when fetching meeting information
2028
+ * @public
2029
+ * @memberof Meeting
2030
+ * @returns {Promise<{isRegistrationIdValid: boolean, requiredCaptcha: boolean, failureReason: MEETING_INFO_FAILURE_REASON}>}
2031
+ */
2032
+ public verifyRegistrationId(registrationId: string, captchaCode: string, sendCAevents = false) {
2033
+ return this.fetchMeetingInfo({
2034
+ registrationId,
2035
+ captchaCode,
2036
+ sendCAevents,
2037
+ })
2038
+ .then(() => {
2039
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_REGISTRATION_ID_SUCCESS);
2040
+
2041
+ return {
2042
+ isRegistrationIdValid: true,
2043
+ requiredCaptcha: null,
2044
+ failureReason: MEETING_INFO_FAILURE_REASON.NONE,
2045
+ };
2046
+ })
2047
+ .catch((error) => {
2048
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_REGISTRATION_ID_ERROR);
2049
+
2050
+ if (error instanceof JoinWebinarError || error instanceof CaptchaError) {
2051
+ return {
2052
+ isRegistrationIdValid: this.registrationIdStatus === REGISTRATION_ID_STATUS.VERIFIED,
2053
+ requiredCaptcha: this.requiredCaptcha,
2054
+ failureReason:
2055
+ error instanceof JoinWebinarError
2056
+ ? MEETING_INFO_FAILURE_REASON.WRONG_REGISTRATION_ID
2057
+ : this.meetingInfoFailureReason,
2058
+ };
2059
+ }
2060
+ throw error;
2061
+ });
2062
+ }
2063
+
1982
2064
  /**
1983
2065
  * Refreshes the captcha. As a result the meeting will have new captcha id, image and audio.
1984
2066
  * If the refresh operation fails, meeting remains with the old captcha properties.
@@ -3349,6 +3431,10 @@ export default class Meeting extends StatelessWebexPlugin {
3349
3431
  // The second on is if the audio is muted, we need to tell the statsAnalyzer when
3350
3432
  // the audio is muted or the user is not willing to send media
3351
3433
  this.locusInfo.on(LOCUSINFO.EVENTS.MEDIA_STATUS_CHANGE, (status) => {
3434
+ LoggerProxy.logger.info(
3435
+ 'Meeting:index#setUpLocusInfoSelfListener --> MEDIA_STATUS_CHANGE received, processing...'
3436
+ );
3437
+
3352
3438
  if (this.statsAnalyzer) {
3353
3439
  this.statsAnalyzer.updateMediaStatus({
3354
3440
  actual: status,
@@ -3362,6 +3448,10 @@ export default class Meeting extends StatelessWebexPlugin {
3362
3448
  receiveShare: this.mediaProperties.mediaDirection?.receiveShare,
3363
3449
  },
3364
3450
  });
3451
+ } else {
3452
+ LoggerProxy.logger.warn(
3453
+ 'Meeting:index#setUpLocusInfoSelfListener --> MEDIA_STATUS_CHANGE, statsAnalyzer is not available.'
3454
+ );
3365
3455
  }
3366
3456
  });
3367
3457
 
@@ -3407,6 +3497,7 @@ export default class Meeting extends StatelessWebexPlugin {
3407
3497
  });
3408
3498
 
3409
3499
  this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BRB_CHANGED, (payload) => {
3500
+ this.brbState?.handleServerBrbUpdate(payload?.brb?.enabled);
3410
3501
  Trigger.trigger(
3411
3502
  this,
3412
3503
  {
@@ -3650,22 +3741,7 @@ export default class Meeting extends StatelessWebexPlugin {
3650
3741
  return Promise.reject(error);
3651
3742
  }
3652
3743
 
3653
- // this logic should be applied only to multistream meetings
3654
- return this.meetingRequest
3655
- .setBrb({
3656
- enabled,
3657
- locusUrl: this.locusUrl,
3658
- deviceUrl: this.deviceUrl,
3659
- selfId: this.selfId,
3660
- })
3661
- .then(() => {
3662
- this.sendSlotManager.setSourceStateOverride(MediaType.VideoMain, enabled ? 'away' : null);
3663
- })
3664
- .catch((error) => {
3665
- LoggerProxy.logger.error('Meeting:index#beRightBack --> Error ', error);
3666
-
3667
- return Promise.reject(error);
3668
- });
3744
+ return this.brbState.enable(enabled, this.sendSlotManager);
3669
3745
  }
3670
3746
 
3671
3747
  /**
@@ -5718,7 +5794,14 @@ export default class Meeting extends StatelessWebexPlugin {
5718
5794
  return undefined;
5719
5795
  }
5720
5796
  // @ts-ignore - Fix type
5721
- await this.webex.internal.llm.disconnectLLM();
5797
+ await this.webex.internal.llm.disconnectLLM(
5798
+ isJoined
5799
+ ? {
5800
+ code: 3050,
5801
+ reason: 'done (permanent)',
5802
+ }
5803
+ : undefined
5804
+ );
5722
5805
  // @ts-ignore - Fix type
5723
5806
  this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
5724
5807
  }
@@ -6099,9 +6182,12 @@ export default class Meeting extends StatelessWebexPlugin {
6099
6182
  * @returns {undefined}
6100
6183
  */
6101
6184
  public roapMessageReceived = (roapMessage: RoapMessage) => {
6102
- const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
6185
+ const mediaServer =
6186
+ roapMessage.messageType === 'ANSWER'
6187
+ ? MeetingsUtil.getMediaServer(roapMessage.sdp)
6188
+ : undefined;
6103
6189
 
6104
- if (this.isMultistream && mediaServer !== 'homer') {
6190
+ if (this.isMultistream && mediaServer && mediaServer !== 'homer') {
6105
6191
  throw new MultistreamNotSupportedError(
6106
6192
  `Client asked for multistream backend (Homer), but got ${mediaServer} instead`
6107
6193
  );
@@ -6716,6 +6802,9 @@ export default class Meeting extends StatelessWebexPlugin {
6716
6802
  new RtcMetrics(this.webex, {meetingId: this.id}, this.correlationId)
6717
6803
  : undefined;
6718
6804
 
6805
+ // ongoing reachability checks slow down new media connections especially on Firefox, so we stop them
6806
+ this.getWebexObject().meetings.reachability.stopReachability();
6807
+
6719
6808
  const mc = Media.createMediaConnection(
6720
6809
  this.isMultistream,
6721
6810
  this.getMediaConnectionDebugId(),
@@ -7432,6 +7521,7 @@ export default class Meeting extends StatelessWebexPlugin {
7432
7521
 
7433
7522
  this.audio = createMuteState(AUDIO, this, audioEnabled);
7434
7523
  this.video = createMuteState(VIDEO, this, videoEnabled);
7524
+ this.brbState = createBrbState(this, false);
7435
7525
 
7436
7526
  try {
7437
7527
  await this.setUpLocalStreamReferences(localStreams);
@@ -7467,6 +7557,9 @@ export default class Meeting extends StatelessWebexPlugin {
7467
7557
  throw error;
7468
7558
  }
7469
7559
  }
7560
+
7561
+ LoggerProxy.logger.info(`${LOG_HEADER} media connected, finalizing...`);
7562
+
7470
7563
  if (this.mediaProperties.hasLocalShareStream()) {
7471
7564
  await this.enqueueScreenShareFloorRequest();
7472
7565
  }
@@ -379,12 +379,7 @@ export class MuteState {
379
379
  }
380
380
  if (muted !== undefined) {
381
381
  this.state.server.remoteMute = muted;
382
-
383
- // We never want to unmute the local stream from a server remote mute update.
384
- // Moderated unmute is handled by a different function.
385
- if (muted) {
386
- this.muteLocalStream(meeting, muted, 'remotelyMuted');
387
- }
382
+ this.muteLocalStream(meeting, muted, 'remotelyMuted');
388
383
  }
389
384
  }
390
385