@webex/plugin-meetings 3.9.0-next.2 → 3.9.0-next.20

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 (85) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +2 -0
  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/index.js +38 -10
  8. package/dist/locus-info/index.js.map +1 -1
  9. package/dist/locus-info/parser.js +4 -1
  10. package/dist/locus-info/parser.js.map +1 -1
  11. package/dist/media/properties.js +53 -5
  12. package/dist/media/properties.js.map +1 -1
  13. package/dist/meeting/in-meeting-actions.js +2 -0
  14. package/dist/meeting/in-meeting-actions.js.map +1 -1
  15. package/dist/meeting/index.js +189 -122
  16. package/dist/meeting/index.js.map +1 -1
  17. package/dist/meeting/muteState.js +2 -5
  18. package/dist/meeting/muteState.js.map +1 -1
  19. package/dist/meeting/request.js +25 -0
  20. package/dist/meeting/request.js.map +1 -1
  21. package/dist/meeting/util.js +30 -11
  22. package/dist/meeting/util.js.map +1 -1
  23. package/dist/meeting-info/meeting-info-v2.js +29 -21
  24. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  25. package/dist/meetings/index.js +31 -25
  26. package/dist/meetings/index.js.map +1 -1
  27. package/dist/member/types.js.map +1 -1
  28. package/dist/members/collection.js +13 -0
  29. package/dist/members/collection.js.map +1 -1
  30. package/dist/members/index.js +42 -20
  31. package/dist/members/index.js.map +1 -1
  32. package/dist/members/util.js +7 -2
  33. package/dist/members/util.js.map +1 -1
  34. package/dist/metrics/constants.js +2 -1
  35. package/dist/metrics/constants.js.map +1 -1
  36. package/dist/reachability/index.js +3 -3
  37. package/dist/reachability/index.js.map +1 -1
  38. package/dist/types/constants.d.ts +2 -0
  39. package/dist/types/locus-info/index.d.ts +54 -1
  40. package/dist/types/media/properties.d.ts +21 -0
  41. package/dist/types/meeting/in-meeting-actions.d.ts +2 -0
  42. package/dist/types/meeting/index.d.ts +11 -1
  43. package/dist/types/meeting/request.d.ts +9 -0
  44. package/dist/types/meeting/util.d.ts +10 -3
  45. package/dist/types/meeting-info/meeting-info-v2.d.ts +6 -3
  46. package/dist/types/meetings/index.d.ts +3 -1
  47. package/dist/types/member/types.d.ts +1 -0
  48. package/dist/types/members/collection.d.ts +6 -0
  49. package/dist/types/members/index.d.ts +12 -2
  50. package/dist/types/members/util.d.ts +6 -3
  51. package/dist/types/metrics/constants.d.ts +1 -0
  52. package/dist/webinar/index.js +1 -1
  53. package/package.json +16 -16
  54. package/src/constants.ts +2 -0
  55. package/src/locus-info/index.ts +84 -9
  56. package/src/locus-info/parser.ts +5 -1
  57. package/src/media/properties.ts +43 -0
  58. package/src/meeting/in-meeting-actions.ts +4 -0
  59. package/src/meeting/index.ts +91 -4
  60. package/src/meeting/muteState.ts +2 -6
  61. package/src/meeting/request.ts +23 -0
  62. package/src/meeting/util.ts +41 -20
  63. package/src/meeting-info/meeting-info-v2.ts +24 -5
  64. package/src/meetings/index.ts +9 -3
  65. package/src/member/types.ts +1 -0
  66. package/src/members/collection.ts +11 -0
  67. package/src/members/index.ts +38 -5
  68. package/src/members/util.ts +18 -2
  69. package/src/metrics/constants.ts +1 -0
  70. package/src/reachability/index.ts +3 -3
  71. package/test/unit/spec/locus-info/index.js +30 -15
  72. package/test/unit/spec/locus-info/parser.js +3 -2
  73. package/test/unit/spec/media/properties.ts +137 -0
  74. package/test/unit/spec/meeting/in-meeting-actions.ts +2 -0
  75. package/test/unit/spec/meeting/index.js +255 -27
  76. package/test/unit/spec/meeting/muteState.js +32 -6
  77. package/test/unit/spec/meeting/request.js +21 -0
  78. package/test/unit/spec/meeting/utils.js +45 -16
  79. package/test/unit/spec/meeting-info/meetinginfov2.js +8 -3
  80. package/test/unit/spec/meetings/index.js +10 -1
  81. package/test/unit/spec/members/collection.js +120 -0
  82. package/test/unit/spec/members/index.js +72 -3
  83. package/test/unit/spec/members/request.js +55 -0
  84. package/test/unit/spec/members/utils.js +116 -14
  85. package/test/unit/spec/reachability/index.ts +158 -3
@@ -728,13 +728,17 @@ export default class Parser {
728
728
  break;
729
729
 
730
730
  case USE_INCOMING:
731
- case LOCUS_URL_CHANGED:
732
731
  // update working copy for future comparisons.
733
732
  // Note: The working copy of parser gets updated in .onFullLocus()
734
733
  // and here when USE_INCOMING or LOCUS_URL_CHANGED locus.
735
734
  this.workingCopy = newLoci;
736
735
  break;
737
736
 
737
+ case LOCUS_URL_CHANGED:
738
+ // clear the working copy completely, do a full locus sync
739
+ this.workingCopy = null;
740
+ break;
741
+
738
742
  case WAIT:
739
743
  // we've taken newLoci from the front of the queue, so put it back there as we have to wait
740
744
  // for the one that should be in front of it, before we can process it
@@ -9,9 +9,12 @@ import {
9
9
 
10
10
  import {parse} from '@webex/ts-sdp';
11
11
  import {ClientEvent} from '@webex/internal-plugin-metrics';
12
+ import {throttle} from 'lodash';
13
+ import Metrics from '../metrics';
12
14
  import {MEETINGS, QUALITY_LEVELS} from '../constants';
13
15
  import LoggerProxy from '../common/logs/logger-proxy';
14
16
  import MediaConnectionAwaiter from './MediaConnectionAwaiter';
17
+ import BEHAVIORAL_METRICS from '../metrics/constants';
15
18
 
16
19
  export type MediaDirection = {
17
20
  sendAudio: boolean;
@@ -41,6 +44,8 @@ export default class MediaProperties {
41
44
  videoDeviceId: any;
42
45
  videoStream?: LocalCameraStream;
43
46
  namespace = MEETINGS;
47
+ mediaIssueCounters: {[key: string]: number} = {};
48
+ throttledSendMediaIssueMetric: ReturnType<typeof throttle>;
44
49
 
45
50
  /**
46
51
  * @param {Object} [options] -- to auto construct
@@ -66,6 +71,15 @@ export default class MediaProperties {
66
71
  this.remoteQualityLevel = QUALITY_LEVELS.HIGH;
67
72
  this.mediaSettings = {};
68
73
  this.videoDeviceId = null;
74
+
75
+ this.throttledSendMediaIssueMetric = throttle((eventPayload) => {
76
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEDIA_ISSUE_DETECTED, {
77
+ ...eventPayload,
78
+ });
79
+ Object.keys(this.mediaIssueCounters).forEach((key) => {
80
+ this.mediaIssueCounters[key] = 0;
81
+ });
82
+ }, 1000 * 60 * 5); // at most once every 5 minutes
69
83
  }
70
84
 
71
85
  /**
@@ -139,8 +153,14 @@ export default class MediaProperties {
139
153
  this.videoDeviceId = deviceId;
140
154
  }
141
155
 
156
+ /**
157
+ * Clears the webrtcMediaConnection. This method should be called after
158
+ * peer connection is closed and no longer needed.
159
+ * @returns {void}
160
+ */
142
161
  unsetPeerConnection() {
143
162
  this.webrtcMediaConnection = null;
163
+ this.throttledSendMediaIssueMetric.flush();
144
164
  }
145
165
 
146
166
  /**
@@ -424,4 +444,27 @@ export default class MediaProperties {
424
444
  };
425
445
  }
426
446
  }
447
+
448
+ /**
449
+ * Sends a metric about a media issue. Metrics are throttled so that we don't
450
+ * send too many of them, but include a count so that we know how many issues
451
+ * were detected.
452
+ *
453
+ * @param {string} issueType
454
+ * @param {string} issueSubType
455
+ * @param {string} correlationId
456
+ * @returns {void}
457
+ */
458
+ public sendMediaIssueMetric(issueType: string, issueSubType: string, correlationId) {
459
+ const key = `${issueType}_${issueSubType}`;
460
+
461
+ const count = (this.mediaIssueCounters[key] || 0) + 1;
462
+
463
+ this.mediaIssueCounters[key] = count;
464
+
465
+ this.throttledSendMediaIssueMetric({
466
+ correlationId,
467
+ ...this.mediaIssueCounters,
468
+ });
469
+ }
427
470
  }
@@ -44,6 +44,7 @@ interface IInMeetingActions {
44
44
 
45
45
  isManualCaptionActive?: boolean;
46
46
  isSaveTranscriptsEnabled?: boolean;
47
+ isSpokenLanguageAutoDetectionEnabled?: boolean;
47
48
  isWebexAssistantActive?: boolean;
48
49
  canViewCaptionPanel?: boolean;
49
50
  isRealTimeTranslationEnabled?: boolean;
@@ -187,6 +188,8 @@ export default class InMeetingActions implements IInMeetingActions {
187
188
 
188
189
  isSaveTranscriptsEnabled = null;
189
190
 
191
+ isSpokenLanguageAutoDetectionEnabled = null;
192
+
190
193
  isWebexAssistantActive = null;
191
194
 
192
195
  canViewCaptionPanel = null;
@@ -363,6 +366,7 @@ export default class InMeetingActions implements IInMeetingActions {
363
366
  canStopManualCaption: this.canStopManualCaption,
364
367
  isManualCaptionActive: this.isManualCaptionActive,
365
368
  isSaveTranscriptsEnabled: this.isSaveTranscriptsEnabled,
369
+ isSpokenLanguageAutoDetectionEnabled: this.isSpokenLanguageAutoDetectionEnabled,
366
370
  isWebexAssistantActive: this.isWebexAssistantActive,
367
371
  canViewCaptionPanel: this.canViewCaptionPanel,
368
372
  isRealTimeTranslationEnabled: this.isRealTimeTranslationEnabled,
@@ -28,6 +28,8 @@ import {
28
28
  StatsAnalyzerEventNames,
29
29
  NetworkQualityEventNames,
30
30
  NetworkQualityMonitor,
31
+ StatsMonitor,
32
+ StatsMonitorEventNames,
31
33
  } from '@webex/internal-media-core';
32
34
 
33
35
  import {
@@ -269,6 +271,7 @@ export enum ScreenShareFloorStatus {
269
271
  type FetchMeetingInfoParams = {
270
272
  password?: string;
271
273
  registrationId?: string;
274
+ classificationId?: string;
272
275
  captchaCode?: string;
273
276
  extraParams?: Record<string, any>;
274
277
  sendCAevents?: boolean;
@@ -633,6 +636,7 @@ export default class Meeting extends StatelessWebexPlugin {
633
636
  shareStatus: string;
634
637
  screenShareFloorState: ScreenShareFloorStatus;
635
638
  statsAnalyzer: StatsAnalyzer;
639
+ statsMonitor: StatsMonitor;
636
640
  transcription: Transcription;
637
641
  updateMediaConnections: (mediaConnections: any[]) => void;
638
642
  userDisplayHints: any;
@@ -1286,6 +1290,13 @@ export default class Meeting extends StatelessWebexPlugin {
1286
1290
  * @memberof Meeting
1287
1291
  */
1288
1292
  this.networkQualityMonitor = null;
1293
+ /**
1294
+ * @instance
1295
+ * @type {StatsMonitor}
1296
+ * @private
1297
+ * @memberof Meeting
1298
+ */
1299
+ this.statsMonitor = null;
1289
1300
  /**
1290
1301
  * Indicates network status of the webrtc media connection
1291
1302
  * @instance
@@ -1902,6 +1913,7 @@ export default class Meeting extends StatelessWebexPlugin {
1902
1913
  extraParams = {},
1903
1914
  sendCAevents = false,
1904
1915
  registrationId = null,
1916
+ classificationId = null,
1905
1917
  }): Promise<void> {
1906
1918
  try {
1907
1919
  const captchaInfo = captchaCode
@@ -1918,7 +1930,9 @@ export default class Meeting extends StatelessWebexPlugin {
1918
1930
  this.locusId,
1919
1931
  extraParams,
1920
1932
  {meetingId: this.id, sendCAevents},
1921
- registrationId
1933
+ registrationId,
1934
+ null,
1935
+ classificationId
1922
1936
  );
1923
1937
 
1924
1938
  this.parseMeetingInfo(info?.body, this.destination, info?.errors);
@@ -3184,6 +3198,14 @@ export default class Meeting extends StatelessWebexPlugin {
3184
3198
  this.shareCAEventSentStatus.receiveStart = false;
3185
3199
  this.shareCAEventSentStatus.receiveStop = false;
3186
3200
 
3201
+ let finalBeneficiaryId = contentShare.beneficiaryId;
3202
+ // In case of attendee in webinar, the whiteboard is shared by other participants
3203
+ if (this.locusInfo?.info?.isWebinar && this.webinar?.selfIsAttendee) {
3204
+ if (!finalBeneficiaryId && whiteboardShare.beneficiaryId) {
3205
+ finalBeneficiaryId = whiteboardShare.beneficiaryId;
3206
+ }
3207
+ }
3208
+
3187
3209
  Trigger.trigger(
3188
3210
  this,
3189
3211
  {
@@ -3192,7 +3214,7 @@ export default class Meeting extends StatelessWebexPlugin {
3192
3214
  },
3193
3215
  EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
3194
3216
  {
3195
- memberId: contentShare.beneficiaryId,
3217
+ memberId: finalBeneficiaryId,
3196
3218
  url: contentShare.url,
3197
3219
  shareInstanceId: this.remoteShareInstanceId,
3198
3220
  annotationInfo: contentShare.annotation,
@@ -4214,6 +4236,9 @@ export default class Meeting extends StatelessWebexPlugin {
4214
4236
  isLocalRecordingPaused: MeetingUtil.isLocalRecordingPaused(this.userDisplayHints),
4215
4237
  isManualCaptionActive: MeetingUtil.isManualCaptionActive(this.userDisplayHints),
4216
4238
  isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(this.userDisplayHints),
4239
+ isSpokenLanguageAutoDetectionEnabled: MeetingUtil.isSpokenLanguageAutoDetectionEnabled(
4240
+ this.userDisplayHints
4241
+ ),
4217
4242
  isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(this.userDisplayHints),
4218
4243
  canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(this.userDisplayHints),
4219
4244
  isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
@@ -6796,6 +6821,10 @@ export default class Meeting extends StatelessWebexPlugin {
6796
6821
  // @ts-ignore
6797
6822
  this.webex.internal.newMetrics.submitClientEvent({
6798
6823
  name: 'client.ice.start',
6824
+ payload: {
6825
+ // @ts-ignore
6826
+ labels: MeetingUtil.getCaEventLabelsForIpVersion(this.webex),
6827
+ },
6799
6828
  options: {
6800
6829
  meetingId: this.id,
6801
6830
  },
@@ -6965,10 +6994,10 @@ export default class Meeting extends StatelessWebexPlugin {
6965
6994
  }
6966
6995
  }
6967
6996
 
6968
- // Count members that are in the meeting.
6997
+ // Count members that are in the meeting or in the lobby.
6969
6998
  const {members} = this.getMembers().membersCollection;
6970
6999
  event.data.intervalMetadata.meetingUserCount = Object.values(members).filter(
6971
- (member: Member) => member.isInMeeting
7000
+ (member: Member) => member.isInMeeting || member.isInLobby
6972
7001
  ).length;
6973
7002
 
6974
7003
  // @ts-ignore
@@ -7327,10 +7356,12 @@ export default class Meeting extends StatelessWebexPlugin {
7327
7356
  if (this.config.stats.enableStatsAnalyzer) {
7328
7357
  // @ts-ignore - config coming from registerPlugin
7329
7358
  this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
7359
+ this.statsMonitor = new StatsMonitor();
7330
7360
  this.statsAnalyzer = new StatsAnalyzer({
7331
7361
  // @ts-ignore - config coming from registerPlugin
7332
7362
  config: this.config.stats,
7333
7363
  networkQualityMonitor: this.networkQualityMonitor,
7364
+ statsMonitor: this.statsMonitor,
7334
7365
  isMultistream: this.isMultistream,
7335
7366
  });
7336
7367
  this.shareCAEventSentStatus = {
@@ -7344,6 +7375,33 @@ export default class Meeting extends StatelessWebexPlugin {
7344
7375
  NetworkQualityEventNames.NETWORK_QUALITY,
7345
7376
  this.sendNetworkQualityEvent.bind(this)
7346
7377
  );
7378
+
7379
+ this.statsMonitor.on(StatsMonitorEventNames.INBOUND_AUDIO_ISSUE, (data) => {
7380
+ // Before forwarding any inbound audio issues to the app, make sure that we have at least one other
7381
+ // participant in the meeting with unmuted audio.
7382
+ // We don't check this.mediaProperties.mediaDirection here, because that's already handled in statsAnalyzer,
7383
+ // so we won't get this event if we are not setup to receive any audio
7384
+ const atLeastOneUnmutedOtherMember = Object.values(
7385
+ this.members.membersCollection.getAll()
7386
+ ).find((member) => {
7387
+ return !member.isSelf && !member.isPairedWithSelf && !member.isAudioMuted;
7388
+ });
7389
+
7390
+ if (atLeastOneUnmutedOtherMember) {
7391
+ this.mediaProperties.sendMediaIssueMetric(
7392
+ 'inbound_audio',
7393
+ data.issueSubType,
7394
+ this.correlationId
7395
+ );
7396
+
7397
+ Trigger.trigger(
7398
+ this,
7399
+ {file: 'meeting/index', function: 'createStatsAnalyzer'},
7400
+ EVENT_TRIGGERS.MEDIA_INBOUND_AUDIO_ISSUE_DETECTED,
7401
+ data
7402
+ );
7403
+ }
7404
+ });
7347
7405
  }
7348
7406
  }
7349
7407
 
@@ -7642,6 +7700,10 @@ export default class Meeting extends StatelessWebexPlugin {
7642
7700
  }
7643
7701
 
7644
7702
  this.statsAnalyzer = null;
7703
+ this.networkQualityMonitor?.removeAllListeners();
7704
+ this.networkQualityMonitor = null;
7705
+ this.statsMonitor?.removeAllListeners();
7706
+ this.statsMonitor = null;
7645
7707
 
7646
7708
  // when media fails, we want to upload a webrtc dump to see whats going on
7647
7709
  // this function is async, but returns once the stats have been gathered
@@ -7665,6 +7727,10 @@ export default class Meeting extends StatelessWebexPlugin {
7665
7727
  await this.statsAnalyzer.stopAnalyzer();
7666
7728
  }
7667
7729
  this.statsAnalyzer = null;
7730
+ this.networkQualityMonitor?.removeAllListeners();
7731
+ this.networkQualityMonitor = null;
7732
+ this.statsMonitor?.removeAllListeners();
7733
+ this.statsMonitor = null;
7668
7734
 
7669
7735
  this.isMultistream = false;
7670
7736
 
@@ -7836,6 +7902,9 @@ export default class Meeting extends StatelessWebexPlugin {
7836
7902
 
7837
7903
  this.allowMediaInLobby = options?.allowMediaInLobby;
7838
7904
 
7905
+ // @ts-ignore
7906
+ const ipver = MeetingUtil.getIpVersion(this.webex); // used just for metrics
7907
+
7839
7908
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
7840
7909
  // @ts-ignore - isUserUnadmitted coming from SelfUtil
7841
7910
  if (this.isUserUnadmitted && !this.wirelessShare && !this.allowMediaInLobby) {
@@ -7934,6 +8003,7 @@ export default class Meeting extends StatelessWebexPlugin {
7934
8003
  locus_id: this.locusUrl.split('/').pop(),
7935
8004
  connectionType,
7936
8005
  ipVersion,
8006
+ ipver,
7937
8007
  selectedCandidatePairChanges,
7938
8008
  numTransports,
7939
8009
  isMultistream: this.isMultistream,
@@ -8002,6 +8072,7 @@ export default class Meeting extends StatelessWebexPlugin {
8002
8072
  ...reachabilityMetrics,
8003
8073
  ...iceCandidateErrors,
8004
8074
  iceCandidatesCount: this.iceCandidatesCount,
8075
+ ipver,
8005
8076
  });
8006
8077
 
8007
8078
  await this.cleanUpOnAddMediaFailure();
@@ -9907,4 +9978,20 @@ export default class Meeting extends StatelessWebexPlugin {
9907
9978
 
9908
9979
  return this.meetingRequest.synchronizeStage(this.locusUrl, videoLayout);
9909
9980
  }
9981
+
9982
+ /**
9983
+ * Notifies the host with the given meeting UUID and display names.
9984
+ *
9985
+ * @param {string} meetingUuid - The UUID of the meeting.
9986
+ * @param {string[]} displayName - An array of display names to notify the host with.
9987
+ * @returns {Promise<any>} The result of the notifyHost request.
9988
+ */
9989
+ notifyHost(meetingUuid: string, displayName: string[]) {
9990
+ return this.meetingRequest.notifyHost(
9991
+ this.meetingInfo.siteFullUrl,
9992
+ this.locusId,
9993
+ meetingUuid,
9994
+ displayName
9995
+ );
9996
+ }
9910
9997
  }
@@ -291,18 +291,14 @@ export class MuteState {
291
291
  );
292
292
 
293
293
  return MeetingUtil.remoteUpdateAudioVideo(meeting, audioMuted, videoMuted)
294
- .then((locus) => {
294
+ .then((response) => {
295
295
  LoggerProxy.logger.info(
296
296
  `Meeting:muteState#sendLocalMuteRequestToServer --> ${this.type}: local mute (audio=${audioMuted}, video=${videoMuted}) applied to server`
297
297
  );
298
298
 
299
299
  this.state.server.localMute = this.type === AUDIO ? audioMuted : videoMuted;
300
300
 
301
- if (locus) {
302
- meeting.locusInfo.handleLocusDelta(locus, meeting);
303
- }
304
-
305
- return locus;
301
+ return MeetingUtil.updateLocusFromApiResponse(meeting, response);
306
302
  })
307
303
  .catch((remoteUpdateError) => {
308
304
  LoggerProxy.logger.warn(
@@ -985,4 +985,27 @@ export default class MeetingRequest extends StatelessWebexPlugin {
985
985
  body: {videoLayout},
986
986
  });
987
987
  }
988
+
989
+ /**
990
+ * Sends a request to notify the host of a meeting.
991
+ * @param {string} siteFullUrl - The site URL.
992
+ * @param {string} locusId - The locus ID.
993
+ * @param {string} meetingUuid - The meeting UUID.
994
+ * @param {Array<string>} displayName - The display names to notify the host about.
995
+ * @returns {Promise}
996
+ */
997
+ notifyHost(siteFullUrl: string, locusId: string, meetingUuid: string, displayName: string[]) {
998
+ // @ts-ignore
999
+ return this.request({
1000
+ method: HTTP_VERBS.POST,
1001
+ uri: `https://${siteFullUrl}/wbxappapi/v1/meetings/${meetingUuid}/notifyhost`,
1002
+ body: {
1003
+ displayName,
1004
+ size: displayName?.length,
1005
+ },
1006
+ headers: {
1007
+ locusId,
1008
+ },
1009
+ });
1010
+ }
988
1011
  }
@@ -59,18 +59,16 @@ const MeetingUtil = {
59
59
  );
60
60
  }
61
61
 
62
- return meeting.locusMediaRequest
63
- .send({
64
- type: 'LocalMute',
65
- selfUrl: meeting.selfUrl,
66
- mediaId: meeting.mediaId,
67
- sequence: meeting.locusInfo.sequence,
68
- muteOptions: {
69
- audioMuted,
70
- videoMuted,
71
- },
72
- })
73
- .then((response) => response?.body?.locus);
62
+ return meeting.locusMediaRequest.send({
63
+ type: 'LocalMute',
64
+ selfUrl: meeting.selfUrl,
65
+ mediaId: meeting.mediaId,
66
+ sequence: meeting.locusInfo.sequence,
67
+ muteOptions: {
68
+ audioMuted,
69
+ videoMuted,
70
+ },
71
+ });
74
72
  },
75
73
 
76
74
  hasOwner: (info) => info && info.owner,
@@ -115,6 +113,28 @@ const MeetingUtil = {
115
113
  return IP_VERSION.unknown;
116
114
  },
117
115
 
116
+ /**
117
+ * Returns CA event labels related to Orpheus ipver parameter that can be sent to CA with any CA event
118
+ * @param {any} webex instance
119
+ * @returns {Array<string>|undefined} array of CA event labels or undefined if no labels should be sent
120
+ */
121
+ getCaEventLabelsForIpVersion(webex: any): Array<string> | undefined {
122
+ const ipver = MeetingUtil.getIpVersion(webex);
123
+
124
+ switch (ipver) {
125
+ case IP_VERSION.unknown:
126
+ return undefined;
127
+ case IP_VERSION.only_ipv4:
128
+ return ['hasIpv4_true'];
129
+ case IP_VERSION.only_ipv6:
130
+ return ['hasIpv6_true'];
131
+ case IP_VERSION.ipv4_and_ipv6:
132
+ return ['hasIpv4_true', 'hasIpv6_true'];
133
+ default:
134
+ return undefined;
135
+ }
136
+ },
137
+
118
138
  joinMeeting: async (meeting, options) => {
119
139
  if (!meeting) {
120
140
  return Promise.reject(new ParameterError('You need a meeting object.'));
@@ -618,6 +638,9 @@ const MeetingUtil = {
618
638
  isManualCaptionActive: (displayHints) =>
619
639
  displayHints.includes(DISPLAY_HINTS.MANUAL_CAPTION_STATUS_ACTIVE),
620
640
 
641
+ isSpokenLanguageAutoDetectionEnabled: (displayHints) =>
642
+ displayHints.includes(DISPLAY_HINTS.SPOKEN_LANGUAGE_AUTO_DETECTION_ENABLED),
643
+
621
644
  isWebexAssistantActive: (displayHints) =>
622
645
  displayHints.includes(DISPLAY_HINTS.WEBEX_ASSISTANT_STATUS_ACTIVE),
623
646
 
@@ -673,22 +696,20 @@ const MeetingUtil = {
673
696
  },
674
697
 
675
698
  /**
676
- * Updates the locus info for the meeting with the delta locus
677
- * returned from requests that include the sequence information
699
+ * Updates the locus info for the meeting with the locus
700
+ * information returned from API requests made to Locus
678
701
  * Returns the original response object
679
702
  * @param {Object} meeting The meeting object
680
703
  * @param {Object} response The response of the http request
681
704
  * @returns {Object}
682
705
  */
683
- updateLocusWithDelta: (meeting, response) => {
706
+ updateLocusFromApiResponse: (meeting, response) => {
684
707
  if (!meeting) {
685
708
  return response;
686
709
  }
687
710
 
688
- const locus = response?.body?.locus;
689
-
690
- if (locus) {
691
- meeting.locusInfo.handleLocusDelta(locus, meeting);
711
+ if (response?.body?.locus) {
712
+ meeting.locusInfo.handleLocusAPIResponse(meeting, response.body);
692
713
  }
693
714
 
694
715
  return response;
@@ -735,7 +756,7 @@ const MeetingUtil = {
735
756
 
736
757
  return meeting
737
758
  .request(options)
738
- .then((response) => MeetingUtil.updateLocusWithDelta(meeting, response));
759
+ .then((response) => MeetingUtil.updateLocusFromApiResponse(meeting, response));
739
760
  };
740
761
 
741
762
  return locusDeltaRequest;
@@ -371,6 +371,7 @@ export default class MeetingInfoV2 {
371
371
  * @param {String} conversationUrl conversationUrl to start adhoc meeting on
372
372
  * @param {String} installedOrgID org ID of user's machine
373
373
  * @param {Boolean} enableStaticMeetingLink whether or not to enable static meeting link
374
+ * @param {String} classificationId need it to start adhoc meeting if space support classification
374
375
  * @returns {Promise} returns a meeting info object
375
376
  * @public
376
377
  * @memberof MeetingInfo
@@ -379,7 +380,8 @@ export default class MeetingInfoV2 {
379
380
  conversationUrl: string,
380
381
  installedOrgID?: string,
381
382
  // setting this to true enables static meeting link
382
- enableStaticMeetingLink = false
383
+ enableStaticMeetingLink = false,
384
+ classificationId = undefined
383
385
  ) {
384
386
  const getInvitees = (particpants = []) => {
385
387
  const invitees = [];
@@ -407,6 +409,7 @@ export default class MeetingInfoV2 {
407
409
  invitees: getInvitees(conversation.participants?.items),
408
410
  installedOrgID,
409
411
  schedule: enableStaticMeetingLink,
412
+ classificationId,
410
413
  };
411
414
 
412
415
  if (installedOrgID) {
@@ -429,16 +432,26 @@ export default class MeetingInfoV2 {
429
432
  * Creates adhoc space meetings for a space by fetching the conversation infomation
430
433
  * @param {String} conversationUrl conversationUrl to start adhoc meeting on
431
434
  * @param {String} installedOrgID org ID of user's machine
435
+ * @param {String} classificationId if space is support classification, it needs provide it during start instant meeting
432
436
  * @returns {Promise} returns a meeting info object
433
437
  * @public
434
438
  * @memberof MeetingInfo
435
439
  */
436
- async createAdhocSpaceMeeting(conversationUrl: string, installedOrgID?: string) {
440
+ async createAdhocSpaceMeeting(
441
+ conversationUrl: string,
442
+ installedOrgID?: string,
443
+ classificationId?: string
444
+ ) {
437
445
  if (!this.webex.meetings.preferredWebexSite) {
438
446
  throw Error('No preferred webex site found');
439
447
  }
440
448
 
441
- return this.createAdhocSpaceMeetingOrEnableStaticMeetingLink(conversationUrl, installedOrgID)
449
+ return this.createAdhocSpaceMeetingOrEnableStaticMeetingLink(
450
+ conversationUrl,
451
+ installedOrgID,
452
+ false,
453
+ classificationId
454
+ )
442
455
  .then((requestResult) => {
443
456
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADHOC_MEETING_SUCCESS);
444
457
 
@@ -618,6 +631,7 @@ export default class MeetingInfoV2 {
618
631
  * @param {Object} options
619
632
  * @param {String} registrationId
620
633
  * @param {String} fullSiteUrl
634
+ * @param {String} classificationId
621
635
  * @returns {Promise} returns a meeting info object
622
636
  * @public
623
637
  * @memberof MeetingInfo
@@ -635,7 +649,8 @@ export default class MeetingInfoV2 {
635
649
  extraParams: object = {},
636
650
  options: {meetingId?: string; sendCAevents?: boolean} = {},
637
651
  registrationId: string = null,
638
- fullSiteUrl: string = null
652
+ fullSiteUrl: string = null,
653
+ classificationId: string = null
639
654
  ) {
640
655
  const {meetingId, sendCAevents} = options;
641
656
 
@@ -650,7 +665,11 @@ export default class MeetingInfoV2 {
650
665
  this.webex.config.meetings.experimental.enableAdhocMeetings &&
651
666
  this.webex.meetings.preferredWebexSite
652
667
  ) {
653
- return this.createAdhocSpaceMeeting(destinationType.destination, installedOrgID);
668
+ return this.createAdhocSpaceMeeting(
669
+ destinationType.destination,
670
+ installedOrgID,
671
+ classificationId
672
+ );
654
673
  }
655
674
 
656
675
  const body = await MeetingInfoUtil.getRequestBody({
@@ -1331,6 +1331,7 @@ export default class Meetings extends WebexPlugin {
1331
1331
  * @param {Object} [meetingInfo] - Pre-fetched complete meeting info
1332
1332
  * @param {String} [meetingLookupUrl] - meeting info prefetch url
1333
1333
  * @param {string} sessionCorrelationId - the optional specified sessionCorrelationId (callStateForMetrics.sessionCorrelationId) can be provided instead
1334
+ * @param {String} classificationId - If space support classification, it will provide it while start instant meeting
1334
1335
  * @returns {Promise<Meeting>} A new Meeting.
1335
1336
  * @public
1336
1337
  * @memberof Meetings
@@ -1345,7 +1346,8 @@ export default class Meetings extends WebexPlugin {
1345
1346
  callStateForMetrics: CallStateForMetrics = undefined,
1346
1347
  meetingInfo = undefined,
1347
1348
  meetingLookupUrl = undefined,
1348
- sessionCorrelationId: string = undefined
1349
+ sessionCorrelationId: string = undefined,
1350
+ classificationId: string = undefined
1349
1351
  ) {
1350
1352
  // Validate meeting information based on the provided destination and
1351
1353
  // type. This must be performed prior to determining if the meeting is
@@ -1415,7 +1417,8 @@ export default class Meetings extends WebexPlugin {
1415
1417
  callStateForMetrics,
1416
1418
  failOnMissingMeetingInfo,
1417
1419
  meetingInfo,
1418
- meetingLookupUrl
1420
+ meetingLookupUrl,
1421
+ classificationId
1419
1422
  ).then((createdMeeting: any) => {
1420
1423
  // If the meeting was successfully created.
1421
1424
  if (createdMeeting && createdMeeting.on) {
@@ -1529,6 +1532,7 @@ export default class Meetings extends WebexPlugin {
1529
1532
  * @param {Boolean} failOnMissingMeetingInfo - whether to throw an error if meeting info fails to fetch (for calls that are not 1:1 or content share)
1530
1533
  * @param {Object} [meetingInfo] - Pre-fetched complete meeting info
1531
1534
  * @param {String} [meetingLookupUrl] - meeting info prefetch url
1535
+ * @param {String} classificationId see create()
1532
1536
  * @returns {Promise} a new meeting instance complete with meeting info and destination
1533
1537
  * @private
1534
1538
  * @memberof Meetings
@@ -1541,7 +1545,8 @@ export default class Meetings extends WebexPlugin {
1541
1545
  callStateForMetrics: CallStateForMetrics = undefined,
1542
1546
  failOnMissingMeetingInfo = false,
1543
1547
  meetingInfo = undefined,
1544
- meetingLookupUrl = undefined
1548
+ meetingLookupUrl = undefined,
1549
+ classificationId = undefined
1545
1550
  ) {
1546
1551
  const meeting = new Meeting(
1547
1552
  {
@@ -1589,6 +1594,7 @@ export default class Meetings extends WebexPlugin {
1589
1594
  // @ts-ignore
1590
1595
  const {enableUnifiedMeetings} = this.config.experimental;
1591
1596
  const meetingInfoOptions = {
1597
+ classificationId,
1592
1598
  extraParams: infoExtraParams,
1593
1599
  sendCAevents: !!callStateForMetrics?.correlationId, // if client sends correlation id as argument of public create(), then it means that this meeting creation is part of a pre-join intent from user
1594
1600
  };
@@ -109,4 +109,5 @@ export interface Participant {
109
109
  status: ParticipantMediaStatus;
110
110
  type: string;
111
111
  url: ParticipantUrl;
112
+ isRemoved: boolean; // JS-SDK internal field to indicate in updates when the participant is removed
112
113
  }
@@ -39,6 +39,17 @@ export default class MembersCollection {
39
39
  return this.members;
40
40
  }
41
41
 
42
+ /**
43
+ * Removes a member from the collection
44
+ * @param {String} id
45
+ * @returns {void}
46
+ */
47
+ remove(id: string) {
48
+ if (this.members[id]) {
49
+ delete this.members[id];
50
+ }
51
+ }
52
+
42
53
  /**
43
54
  * @returns {void}
44
55
  * reset members