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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.js +1 -0
  4. package/dist/config.js.map +1 -1
  5. package/dist/constants.js +1 -0
  6. package/dist/constants.js.map +1 -1
  7. package/dist/interpretation/index.js +4 -4
  8. package/dist/interpretation/index.js.map +1 -1
  9. package/dist/interpretation/siLanguage.js +1 -1
  10. package/dist/locus-info/controlsUtils.js +1 -1
  11. package/dist/locus-info/controlsUtils.js.map +1 -1
  12. package/dist/meeting/index.js +69 -4
  13. package/dist/meeting/index.js.map +1 -1
  14. package/dist/meeting/locusMediaRequest.js +21 -5
  15. package/dist/meeting/locusMediaRequest.js.map +1 -1
  16. package/dist/meeting/util.js +2 -0
  17. package/dist/meeting/util.js.map +1 -1
  18. package/dist/meetings/index.js +1 -1
  19. package/dist/meetings/index.js.map +1 -1
  20. package/dist/multistream/remoteMediaManager.js +40 -8
  21. package/dist/multistream/remoteMediaManager.js.map +1 -1
  22. package/dist/reachability/clusterReachability.js +52 -8
  23. package/dist/reachability/clusterReachability.js.map +1 -1
  24. package/dist/reachability/index.js +70 -45
  25. package/dist/reachability/index.js.map +1 -1
  26. package/dist/reachability/reachability.types.js +14 -0
  27. package/dist/reachability/reachability.types.js.map +1 -1
  28. package/dist/recording-controller/util.js +5 -5
  29. package/dist/recording-controller/util.js.map +1 -1
  30. package/dist/types/config.d.ts +1 -0
  31. package/dist/types/constants.d.ts +1 -0
  32. package/dist/types/meeting/index.d.ts +30 -0
  33. package/dist/types/multistream/remoteMediaManager.d.ts +10 -1
  34. package/dist/types/reachability/clusterReachability.d.ts +13 -1
  35. package/dist/types/reachability/index.d.ts +2 -1
  36. package/dist/types/reachability/reachability.types.d.ts +5 -0
  37. package/dist/webinar/index.js +1 -1
  38. package/package.json +22 -22
  39. package/src/config.ts +1 -0
  40. package/src/constants.ts +1 -0
  41. package/src/interpretation/index.ts +3 -3
  42. package/src/locus-info/controlsUtils.ts +2 -2
  43. package/src/meeting/index.ts +66 -3
  44. package/src/meeting/locusMediaRequest.ts +27 -4
  45. package/src/meeting/util.ts +1 -1
  46. package/src/meetings/index.ts +1 -1
  47. package/src/multistream/remoteMediaManager.ts +32 -10
  48. package/src/reachability/clusterReachability.ts +47 -1
  49. package/src/reachability/index.ts +15 -0
  50. package/src/reachability/reachability.types.ts +6 -0
  51. package/src/recording-controller/util.ts +17 -13
  52. package/test/unit/spec/interpretation/index.ts +39 -1
  53. package/test/unit/spec/locus-info/controlsUtils.js +8 -0
  54. package/test/unit/spec/meeting/index.js +185 -108
  55. package/test/unit/spec/meeting/locusMediaRequest.ts +96 -58
  56. package/test/unit/spec/meeting/utils.js +44 -0
  57. package/test/unit/spec/meetings/index.js +13 -0
  58. package/test/unit/spec/multistream/remoteMediaManager.ts +397 -118
  59. package/test/unit/spec/reachability/clusterReachability.ts +47 -1
  60. package/test/unit/spec/reachability/index.ts +12 -0
@@ -4,7 +4,7 @@
4
4
  import { Defer } from '@webex/common';
5
5
  import { IP_VERSION } from '../constants';
6
6
  import ReachabilityRequest, { ClusterList } from './request';
7
- import { ClusterReachabilityResult, ClientMediaPreferences, ReachabilityMetrics, ReachabilityReportV0, ReachabilityReportV1, ReachabilityResults, ReachabilityResultsForBackend, GetClustersTrigger } from './reachability.types';
7
+ import { ClusterReachabilityResult, ClientMediaPreferences, ReachabilityMetrics, ReachabilityReportV0, ReachabilityReportV1, ReachabilityResults, ReachabilityResultsForBackend, GetClustersTrigger, NatType } from './reachability.types';
8
8
  import { ClusterReachability } from './clusterReachability';
9
9
  import EventsScope from '../common/events/events-scope';
10
10
  /**
@@ -46,6 +46,7 @@ export default class Reachability extends EventsScope {
46
46
  };
47
47
  startTime: any;
48
48
  totalDuration: any;
49
+ natType: NatType;
49
50
  protected lastTrigger?: string;
50
51
  /**
51
52
  * Creates an instance of Reachability.
@@ -4,6 +4,10 @@ export type TransportResult = {
4
4
  latencyInMilliseconds?: number;
5
5
  clientMediaIPs?: string[];
6
6
  };
7
+ export declare enum NatType {
8
+ Unknown = "unknown",
9
+ SymmetricNat = "symmetric-nat"
10
+ }
7
11
  export type ClusterReachabilityResult = {
8
12
  udp: TransportResult;
9
13
  tcp: TransportResult;
@@ -22,6 +26,7 @@ export type ReachabilityMetrics = {
22
26
  reachability_vmn_tcp_failed: number;
23
27
  reachability_vmn_xtls_success: number;
24
28
  reachability_vmn_xtls_failed: number;
29
+ natType: NatType;
25
30
  };
26
31
  /**
27
32
  * This is the type that matches what backend expects us to send to them. It is a bit weird, because
@@ -458,7 +458,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
458
458
  }, _callee7);
459
459
  }))();
460
460
  },
461
- version: "3.8.0-next.2"
461
+ version: "3.8.0-next.21"
462
462
  });
463
463
  var _default = exports.default = Webinar;
464
464
  //# sourceMappingURL=index.js.map
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.8.0-next.2",
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",
46
+ "@webex/plugin-meetings": "3.8.0-next.21",
47
+ "@webex/plugin-rooms": "3.8.0-next.11",
48
+ "@webex/test-helper-chai": "3.8.0-next.10",
49
+ "@webex/test-helper-mocha": "3.8.0-next.10",
50
+ "@webex/test-helper-mock-webex": "3.8.0-next.10",
51
+ "@webex/test-helper-retry": "3.8.0-next.10",
52
+ "@webex/test-helper-test-users": "3.8.0-next.10",
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-next.18",
64
+ "@webex/common": "3.8.0-next.10",
65
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.2",
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",
66
+ "@webex/internal-media-core": "2.14.7",
67
+ "@webex/internal-plugin-conversation": "3.8.0-next.11",
68
+ "@webex/internal-plugin-device": "3.8.0-next.10",
69
+ "@webex/internal-plugin-llm": "3.8.0-next.12",
70
+ "@webex/internal-plugin-mercury": "3.8.0-next.11",
71
+ "@webex/internal-plugin-metrics": "3.8.0-next.10",
72
+ "@webex/internal-plugin-support": "3.8.0-next.11",
73
+ "@webex/internal-plugin-user": "3.8.0-next.10",
74
+ "@webex/internal-plugin-voicea": "3.8.0-next.21",
75
+ "@webex/media-helpers": "3.8.0-next.11",
76
+ "@webex/plugin-people": "3.8.0-next.11",
77
+ "@webex/plugin-rooms": "3.8.0-next.11",
78
78
  "@webex/web-capabilities": "^1.4.0",
79
- "@webex/webex-core": "3.7.0-next.18",
79
+ "@webex/webex-core": "3.8.0-next.10",
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.8.0-next.2"
95
+ "version": "3.8.0-next.21"
96
96
  }
package/src/config.ts CHANGED
@@ -95,6 +95,7 @@ export default {
95
95
  // This only applies to non-multistream meetings
96
96
  iceCandidatesGatheringTimeout: undefined,
97
97
  backendIpv6NativeSupport: false,
98
+ enableReachabilityChecks: true,
98
99
  reachabilityGetClusterTimeout: 5000,
99
100
  logUploadIntervalMultiplicationFactor: 0, // if set to 0 or undefined, logs won't be uploaded periodically, if you want periodic logs, recommended value is 1
100
101
  },
package/src/constants.ts CHANGED
@@ -904,6 +904,7 @@ export enum SELF_POLICY {
904
904
  ENFORCE_VIRTUAL_BACKGROUND = 'enforceVirtualBackground',
905
905
  SUPPORT_LOCAL_RECORD = 'supportLocalRecord',
906
906
  SUPPORT_NETWORK_BASED_RECORD = 'supportNetworkBasedRecord',
907
+ SUPPORT_PREMISE_RECORD = 'supportPremiseRecord',
907
908
  SUPPORT_REALTIME_CLOSE_CAPTION = 'supportRealtimeCloseCaption',
908
909
  SUPPORT_CHAT = 'supportChat',
909
910
  SUPPORT_DESKTOP_SHARE_REMOTE = 'supportDesktopShareRemote',
@@ -35,13 +35,13 @@ const SimultaneousInterpretation = WebexPlugin.extend({
35
35
  derived: {
36
36
  shouldQuerySupportLanguages: {
37
37
  cache: false,
38
- deps: ['canManageInterpreters', 'hostSIEnabled'],
38
+ deps: ['canManageInterpreters', 'hostSIEnabled', 'locusUrl'],
39
39
  /**
40
40
  * Returns should query support languages or not
41
41
  * @returns {boolean}
42
42
  */
43
43
  fn() {
44
- return !!(this.canManageInterpreters && this.hostSIEnabled);
44
+ return !!(this.canManageInterpreters && this.hostSIEnabled && this.locusUrl);
45
45
  },
46
46
  },
47
47
  },
@@ -51,7 +51,7 @@ const SimultaneousInterpretation = WebexPlugin.extend({
51
51
  */
52
52
  initialize() {
53
53
  this.listenTo(this, 'change:shouldQuerySupportLanguages', () => {
54
- if (this.canManageInterpreters && !this.supportLanguages) {
54
+ if (this.shouldQuerySupportLanguages && !this.supportLanguages) {
55
55
  this.querySupportLanguages();
56
56
  }
57
57
  });
@@ -206,8 +206,8 @@ ControlsUtils.getControls = (oldControls: any, newControls: any) => {
206
206
  ),
207
207
 
208
208
  hasPracticeSessionEnabledChanged: !isEqual(
209
- previous?.practiceSession?.enabled,
210
- current?.practiceSession?.enabled
209
+ !!previous?.practiceSession?.enabled,
210
+ !!current?.practiceSession?.enabled
211
211
  ),
212
212
 
213
213
  hasStageViewChanged: !isEqual(previous?.videoLayout, current?.videoLayout),
@@ -241,6 +241,8 @@ export type CallStateForMetrics = {
241
241
  sessionCorrelationId?: string;
242
242
  joinTrigger?: string;
243
243
  loginType?: string;
244
+ userNameInput?: string;
245
+ emailInput?: string;
244
246
  };
245
247
 
246
248
  export const MEDIA_UPDATE_TYPE = {
@@ -1627,6 +1629,38 @@ export default class Meeting extends StatelessWebexPlugin {
1627
1629
  this.callStateForMetrics.correlationId = correlationId;
1628
1630
  }
1629
1631
 
1632
+ /**
1633
+ * Getter - Returns callStateForMetrics.userNameInput
1634
+ * @returns {string}
1635
+ */
1636
+ get userNameInput() {
1637
+ return this.callStateForMetrics?.userNameInput;
1638
+ }
1639
+
1640
+ /**
1641
+ * Setter - sets callStateForMetrics.userNameInput
1642
+ * @param {string} userNameInput
1643
+ */
1644
+ set userNameInput(userNameInput: string) {
1645
+ this.callStateForMetrics.userNameInput = userNameInput;
1646
+ }
1647
+
1648
+ /**
1649
+ * Getter - Returns callStateForMetrics.emailInput
1650
+ * @returns {string}
1651
+ */
1652
+ get emailInput() {
1653
+ return this.callStateForMetrics?.emailInput;
1654
+ }
1655
+
1656
+ /**
1657
+ * Setter - sets callStateForMetrics.emailInput
1658
+ * @param {string} emailInput
1659
+ */
1660
+ set emailInput(emailInput: string) {
1661
+ this.callStateForMetrics.emailInput = emailInput;
1662
+ }
1663
+
1630
1664
  /**
1631
1665
  * Getter - Returns callStateForMetrics.sessionCorrelationId
1632
1666
  * @returns {string}
@@ -1652,6 +1686,33 @@ export default class Meeting extends StatelessWebexPlugin {
1652
1686
  return this.#isoLocalClientMeetingJoinTime;
1653
1687
  }
1654
1688
 
1689
+ /**
1690
+ * Setter - sets isoLocalClientMeetingJoinTime
1691
+ * This will be set once on meeting join, and not updated again
1692
+ * this will always produce an ISO string
1693
+ * If the iso string is invalid, it will fallback to the current system time
1694
+ * @param {string | undefined} time
1695
+ */
1696
+ set isoLocalClientMeetingJoinTime(time: string | undefined) {
1697
+ const fallback = new Date().toISOString();
1698
+ if (!time) {
1699
+ this.#isoLocalClientMeetingJoinTime = fallback;
1700
+ } else {
1701
+ const date = new Date(time);
1702
+
1703
+ // Check if the date is valid
1704
+ if (Number.isNaN(date.getTime())) {
1705
+ LoggerProxy.logger.info(
1706
+ // @ts-ignore
1707
+ `Meeting:index#isoLocalClientMeetingJoinTime --> Invalid date provided: ${time}. Falling back to system clock.`
1708
+ );
1709
+ this.#isoLocalClientMeetingJoinTime = fallback;
1710
+ } else {
1711
+ this.#isoLocalClientMeetingJoinTime = date.toISOString();
1712
+ }
1713
+ }
1714
+ }
1715
+
1655
1716
  /**
1656
1717
  * Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
1657
1718
  * @param {any} info
@@ -5684,8 +5745,6 @@ export default class Meeting extends StatelessWebexPlugin {
5684
5745
  // @ts-ignore
5685
5746
  this.webex.internal.device.meetingStarted();
5686
5747
 
5687
- this.#isoLocalClientMeetingJoinTime = new Date().toISOString();
5688
-
5689
5748
  LoggerProxy.logger.log('Meeting:index#join --> Success');
5690
5749
 
5691
5750
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
@@ -8681,6 +8740,9 @@ export default class Meeting extends StatelessWebexPlugin {
8681
8740
  LoggerProxy.logger.log(
8682
8741
  `Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
8683
8742
  );
8743
+
8744
+ const shareVideoStreamSettings = this.mediaProperties?.shareVideoStream?.getSettings();
8745
+
8684
8746
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
8685
8747
  correlationId: this.correlationId,
8686
8748
  muted,
@@ -8689,8 +8751,9 @@ export default class Meeting extends StatelessWebexPlugin {
8689
8751
  // SDK to TypeScript 5, which may affect other packages, use bracket notation for now, since
8690
8752
  // all we're doing here is adding metrics.
8691
8753
  // eslint-disable-next-line dot-notation
8692
- displaySurface: this.mediaProperties?.shareVideoStream?.getSettings()['displaySurface'],
8754
+ displaySurface: shareVideoStreamSettings?.['displaySurface'],
8693
8755
  isMultistream: this.isMultistream,
8756
+ frameRate: shareVideoStreamSettings?.frameRate,
8694
8757
  });
8695
8758
  };
8696
8759
 
@@ -1,6 +1,7 @@
1
1
  /* eslint-disable valid-jsdoc */
2
2
  import {defer} from 'lodash';
3
- import {Defer} from '@webex/common';
3
+ import {Defer, transferEvents} from '@webex/common';
4
+ import {EventEmitter} from 'events';
4
5
  import {WebexPlugin} from '@webex/webex-core';
5
6
  import {MEDIA, HTTP_VERBS, ROAP} from '../constants';
6
7
  import LoggerProxy from '../common/logs/logger-proxy';
@@ -250,12 +251,19 @@ export class LocusMediaRequest extends WebexPlugin {
250
251
  this.confluenceState = 'creation in progress';
251
252
  }
252
253
 
253
- // @ts-ignore
254
- return this.request({
254
+ const upload = new EventEmitter();
255
+ const download = new EventEmitter();
256
+
257
+ const options = {
255
258
  method: HTTP_VERBS.PUT,
256
259
  uri,
257
260
  body,
258
- })
261
+ upload,
262
+ download,
263
+ };
264
+
265
+ // @ts-ignore
266
+ const promise = this.request(options)
259
267
  .then((result) => {
260
268
  if (isRequestAffectingConfluenceState(request)) {
261
269
  this.confluenceState = 'created';
@@ -294,6 +302,21 @@ export class LocusMediaRequest extends WebexPlugin {
294
302
 
295
303
  throw e;
296
304
  });
305
+
306
+ if (request.type === 'RoapMessage') {
307
+ const setupProgressListener = (direction: string, eventEmitter: EventEmitter) => {
308
+ eventEmitter.on('progress', (progressEvent: ProgressEvent) => {
309
+ LoggerProxy.logger.info(
310
+ `${request.type}: ${direction} Progress, Timestamp: ${progressEvent.timeStamp}, Progress: ${progressEvent.loaded}/${progressEvent.total}`
311
+ );
312
+ });
313
+ };
314
+
315
+ setupProgressListener('Upload', options.upload);
316
+ setupProgressListener('Download', options.download);
317
+ }
318
+
319
+ return promise;
297
320
  }
298
321
 
299
322
  /**
@@ -180,7 +180,7 @@ const MeetingUtil = {
180
180
  .then((res) => {
181
181
  const parsed = MeetingUtil.parseLocusJoin(res);
182
182
  meeting.setLocus(parsed);
183
-
183
+ meeting.isoLocalClientMeetingJoinTime = res?.headers?.date; // read from header if exist, else fall back to system clock : https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-555657
184
184
  webex.internal.newMetrics.submitClientEvent({
185
185
  name: 'client.locus.join.response',
186
186
  payload: {
@@ -854,7 +854,7 @@ export default class Meetings extends WebexPlugin {
854
854
  this.executeRegistrationStep(
855
855
  () =>
856
856
  this.startReachability('registration').catch((error) => {
857
- LoggerProxy.logger.error(`Meetings:index#register --> GDM error, ${error.message}`);
857
+ LoggerProxy.logger.warn(`Meetings:index#register --> startReachability failed:`, error);
858
858
  }),
859
859
  'startReachability'
860
860
  ),
@@ -1054,22 +1054,44 @@ export class RemoteMediaManager extends EventsScope {
1054
1054
  );
1055
1055
  }
1056
1056
 
1057
+ /**
1058
+ * Set multiple remote video CSIs at once
1059
+ * @param remoteMediaCsis The remote medias and CSIs to set them to
1060
+ * @returns {void}
1061
+ */
1062
+ public setRemoteVideoCsis(remoteMediaCsis: {remoteMedia: RemoteMedia; csi?: CSI | null}[]) {
1063
+ if (!remoteMediaCsis.length) {
1064
+ return;
1065
+ }
1066
+
1067
+ // Check all remote medias are known
1068
+ remoteMediaCsis.forEach(({remoteMedia}) => {
1069
+ if (!Object.values(this.media.video.memberPanes).includes(remoteMedia)) {
1070
+ throw new Error(`remoteMedia ${remoteMedia.id} not found`);
1071
+ }
1072
+ });
1073
+
1074
+ // Set remote video CSIs
1075
+ remoteMediaCsis.forEach(({remoteMedia, csi}) => {
1076
+ if (csi) {
1077
+ remoteMedia.sendMediaRequest(csi, false);
1078
+ } else {
1079
+ remoteMedia.cancelMediaRequest(false);
1080
+ }
1081
+ });
1082
+
1083
+ // Commit the changes
1084
+ this.mediaRequestManagers.video.commit();
1085
+ }
1086
+
1057
1087
  /**
1058
1088
  * Sets a new CSI on a given remote media object
1059
1089
  *
1060
1090
  * @param {RemoteMedia} remoteMedia remote Media object to modify
1061
1091
  * @param {CSI} csi new CSI value, can be null if we want to stop receiving media
1062
1092
  */
1063
- public setRemoteVideoCsi(remoteMedia: RemoteMedia, csi: CSI | null) {
1064
- if (!Object.values(this.media.video.memberPanes).includes(remoteMedia)) {
1065
- throw new Error('remoteMedia not found');
1066
- }
1067
-
1068
- if (csi) {
1069
- remoteMedia.sendMediaRequest(csi, true);
1070
- } else {
1071
- remoteMedia.cancelMediaRequest(true);
1072
- }
1093
+ public setRemoteVideoCsi(remoteMedia: RemoteMedia, csi?: CSI | null) {
1094
+ this.setRemoteVideoCsis([{remoteMedia, csi}]);
1073
1095
  }
1074
1096
 
1075
1097
  /**
@@ -6,7 +6,7 @@ import {convertStunUrlToTurn, convertStunUrlToTurnTls} from './util';
6
6
  import EventsScope from '../common/events/events-scope';
7
7
 
8
8
  import {CONNECTION_STATE, Enum, ICE_GATHERING_STATE} from '../constants';
9
- import {ClusterReachabilityResult} from './reachability.types';
9
+ import {ClusterReachabilityResult, NatType} from './reachability.types';
10
10
 
11
11
  // data for the Events.resultReady event
12
12
  export type ResultEventData = {
@@ -22,9 +22,14 @@ export type ClientMediaIpsUpdatedEventData = {
22
22
  clientMediaIPs: string[];
23
23
  };
24
24
 
25
+ export type NatTypeUpdatedEventData = {
26
+ natType: NatType;
27
+ };
28
+
25
29
  export const Events = {
26
30
  resultReady: 'resultReady', // emitted when a cluster is reached successfully using specific protocol
27
31
  clientMediaIpsUpdated: 'clientMediaIpsUpdated', // emitted when more public IPs are found after resultReady was already sent for a given protocol
32
+ natTypeUpdated: 'natTypeUpdated', // emitted when NAT type is determined
28
33
  } as const;
29
34
 
30
35
  export type Events = Enum<typeof Events>;
@@ -41,6 +46,7 @@ export class ClusterReachability extends EventsScope {
41
46
  private pc?: RTCPeerConnection;
42
47
  private defer: Defer; // this defer is resolved once reachability checks for this cluster are completed
43
48
  private startTimestamp: number;
49
+ private srflxIceCandidates: RTCIceCandidate[] = [];
44
50
  public readonly isVideoMesh: boolean;
45
51
  public readonly name;
46
52
 
@@ -290,6 +296,44 @@ export class ClusterReachability extends EventsScope {
290
296
  }
291
297
  }
292
298
 
299
+ /**
300
+ * Determines NAT Type.
301
+ *
302
+ * @param {RTCIceCandidate} candidate
303
+ * @returns {void}
304
+ */
305
+ private determineNatType(candidate: RTCIceCandidate) {
306
+ this.srflxIceCandidates.push(candidate);
307
+
308
+ if (this.srflxIceCandidates.length > 1) {
309
+ const portsFound: Record<string, Set<number>> = {};
310
+
311
+ this.srflxIceCandidates.forEach((c) => {
312
+ const key = `${c.address}:${c.relatedPort}`;
313
+ if (!portsFound[key]) {
314
+ portsFound[key] = new Set();
315
+ }
316
+ portsFound[key].add(c.port);
317
+ });
318
+
319
+ Object.entries(portsFound).forEach(([, ports]) => {
320
+ if (ports.size > 1) {
321
+ // Found candidates with the same address and relatedPort, but different ports
322
+ this.emit(
323
+ {
324
+ file: 'clusterReachability',
325
+ function: 'determineNatType',
326
+ },
327
+ Events.natTypeUpdated,
328
+ {
329
+ natType: NatType.SymmetricNat,
330
+ }
331
+ );
332
+ }
333
+ });
334
+ }
335
+ }
336
+
293
337
  /**
294
338
  * Registers a listener for the icecandidate event
295
339
  *
@@ -308,6 +352,8 @@ export class ClusterReachability extends EventsScope {
308
352
  if (e.candidate) {
309
353
  if (e.candidate.type === CANDIDATE_TYPES.SERVER_REFLEXIVE) {
310
354
  this.saveResult('udp', latencyInMilliseconds, e.candidate.address);
355
+
356
+ this.determineNatType(e.candidate);
311
357
  }
312
358
 
313
359
  if (e.candidate.type === CANDIDATE_TYPES.RELAY) {
@@ -23,11 +23,13 @@ import {
23
23
  ReachabilityResultsForBackend,
24
24
  TransportResultForBackend,
25
25
  GetClustersTrigger,
26
+ NatType,
26
27
  } from './reachability.types';
27
28
  import {
28
29
  ClientMediaIpsUpdatedEventData,
29
30
  ClusterReachability,
30
31
  Events,
32
+ NatTypeUpdatedEventData,
31
33
  ResultEventData,
32
34
  } from './clusterReachability';
33
35
  import EventsScope from '../common/events/events-scope';
@@ -64,6 +66,7 @@ export default class Reachability extends EventsScope {
64
66
  resultsCount = {videoMesh: {udp: 0}, public: {udp: 0, tcp: 0, xtls: 0}};
65
67
  startTime = undefined;
66
68
  totalDuration = undefined;
69
+ natType = NatType.Unknown;
67
70
 
68
71
  protected lastTrigger?: string;
69
72
 
@@ -143,6 +146,10 @@ export default class Reachability extends EventsScope {
143
146
  * @memberof Reachability
144
147
  */
145
148
  public async gatherReachability(trigger: string): Promise<ReachabilityResults> {
149
+ // @ts-ignore
150
+ if (!this.webex.config.meetings.enableReachabilityChecks) {
151
+ throw new Error('enableReachabilityChecks is disabled in config');
152
+ }
146
153
  // Fetch clusters and measure latency
147
154
  try {
148
155
  this.lastTrigger = trigger;
@@ -305,6 +312,7 @@ export default class Reachability extends EventsScope {
305
312
  reachability_vmn_tcp_failed: 0,
306
313
  reachability_vmn_xtls_success: 0,
307
314
  reachability_vmn_xtls_failed: 0,
315
+ natType: this.natType,
308
316
  };
309
317
 
310
318
  const updateStats = (clusterType: 'public' | 'vmn', result: ClusterReachabilityResult) => {
@@ -963,6 +971,13 @@ export default class Reachability extends EventsScope {
963
971
  }
964
972
  );
965
973
 
974
+ this.clusterReachability[key].on(
975
+ Events.natTypeUpdated,
976
+ async (data: NatTypeUpdatedEventData) => {
977
+ this.natType = data.natType;
978
+ }
979
+ );
980
+
966
981
  this.clusterReachability[key].start(); // not awaiting on purpose
967
982
  });
968
983
  }
@@ -7,6 +7,11 @@ export type TransportResult = {
7
7
  clientMediaIPs?: string[];
8
8
  };
9
9
 
10
+ export enum NatType {
11
+ Unknown = 'unknown',
12
+ SymmetricNat = 'symmetric-nat',
13
+ }
14
+
10
15
  // reachability result for a specific media cluster
11
16
  export type ClusterReachabilityResult = {
12
17
  udp: TransportResult;
@@ -27,6 +32,7 @@ export type ReachabilityMetrics = {
27
32
  reachability_vmn_tcp_failed: number;
28
33
  reachability_vmn_xtls_success: number;
29
34
  reachability_vmn_xtls_failed: number;
35
+ natType: NatType;
30
36
  };
31
37
 
32
38
  /**
@@ -6,33 +6,37 @@ const canUserStart = (
6
6
  displayHints: Array<string>,
7
7
  userPolicies: Record<SELF_POLICY, boolean>
8
8
  ): boolean =>
9
- (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START) ||
10
- displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START)) &&
11
- MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
9
+ (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_START) &&
10
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies)) ||
11
+ (displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_START) &&
12
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies));
12
13
 
13
14
  const canUserPause = (
14
15
  displayHints: Array<string>,
15
16
  userPolicies: Record<SELF_POLICY, boolean>
16
17
  ): boolean =>
17
- (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE) ||
18
- displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE)) &&
19
- MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
18
+ (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_PAUSE) &&
19
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies)) ||
20
+ (displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE) &&
21
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies));
20
22
 
21
23
  const canUserResume = (
22
24
  displayHints: Array<string>,
23
25
  userPolicies: Record<SELF_POLICY, boolean>
24
26
  ): boolean =>
25
- (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME) ||
26
- displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) &&
27
- MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
27
+ (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_RESUME) &&
28
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies)) ||
29
+ (displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME) &&
30
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies));
28
31
 
29
32
  const canUserStop = (
30
33
  displayHints: Array<string>,
31
34
  userPolicies: Record<SELF_POLICY, boolean>
32
35
  ): boolean =>
33
- (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP) ||
34
- displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP)) &&
35
- MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
36
+ (displayHints.includes(DISPLAY_HINTS.RECORDING_CONTROL_STOP) &&
37
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies)) ||
38
+ (displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP) &&
39
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies));
36
40
 
37
41
  const isPremiseRecordingEnabled = (
38
42
  displayHints: Array<string>,
@@ -42,7 +46,7 @@ const isPremiseRecordingEnabled = (
42
46
  displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_PAUSE) ||
43
47
  displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_STOP) ||
44
48
  displayHints.includes(DISPLAY_HINTS.PREMISE_RECORDING_CONTROL_RESUME)) &&
45
- MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_NETWORK_BASED_RECORD, userPolicies);
49
+ MeetingUtil.selfSupportsFeature(SELF_POLICY.SUPPORT_PREMISE_RECORD, userPolicies);
46
50
 
47
51
  const extractLocusId = (url: string) => {
48
52
  return url?.split('/').pop();