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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) 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 +89 -5
  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 +4 -1
  17. package/dist/meeting/util.js.map +1 -1
  18. package/dist/meeting-info/meeting-info-v2.js +359 -60
  19. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  20. package/dist/meetings/index.js +60 -1
  21. package/dist/meetings/index.js.map +1 -1
  22. package/dist/member/index.js +10 -0
  23. package/dist/member/index.js.map +1 -1
  24. package/dist/member/util.js +3 -0
  25. package/dist/member/util.js.map +1 -1
  26. package/dist/metrics/constants.js +9 -0
  27. package/dist/metrics/constants.js.map +1 -1
  28. package/dist/reachability/clusterReachability.js +52 -8
  29. package/dist/reachability/clusterReachability.js.map +1 -1
  30. package/dist/reachability/index.js +70 -45
  31. package/dist/reachability/index.js.map +1 -1
  32. package/dist/reachability/reachability.types.js +14 -0
  33. package/dist/reachability/reachability.types.js.map +1 -1
  34. package/dist/reachability/request.js +19 -3
  35. package/dist/reachability/request.js.map +1 -1
  36. package/dist/recording-controller/util.js +5 -5
  37. package/dist/recording-controller/util.js.map +1 -1
  38. package/dist/types/config.d.ts +1 -0
  39. package/dist/types/constants.d.ts +1 -0
  40. package/dist/types/meeting/index.d.ts +30 -0
  41. package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
  42. package/dist/types/meetings/index.d.ts +29 -0
  43. package/dist/types/member/index.d.ts +1 -0
  44. package/dist/types/metrics/constants.d.ts +9 -0
  45. package/dist/types/reachability/clusterReachability.d.ts +13 -1
  46. package/dist/types/reachability/index.d.ts +2 -1
  47. package/dist/types/reachability/reachability.types.d.ts +5 -0
  48. package/dist/webinar/index.js +1 -1
  49. package/package.json +22 -22
  50. package/src/config.ts +1 -0
  51. package/src/constants.ts +1 -0
  52. package/src/interpretation/index.ts +3 -3
  53. package/src/locus-info/controlsUtils.ts +2 -2
  54. package/src/meeting/index.ts +85 -7
  55. package/src/meeting/locusMediaRequest.ts +27 -4
  56. package/src/meeting/util.ts +2 -1
  57. package/src/meeting-info/meeting-info-v2.ts +247 -6
  58. package/src/meetings/index.ts +72 -1
  59. package/src/member/index.ts +11 -0
  60. package/src/member/util.ts +3 -0
  61. package/src/metrics/constants.ts +9 -0
  62. package/src/reachability/clusterReachability.ts +47 -1
  63. package/src/reachability/index.ts +15 -0
  64. package/src/reachability/reachability.types.ts +6 -0
  65. package/src/reachability/request.ts +7 -0
  66. package/src/recording-controller/util.ts +17 -13
  67. package/test/unit/spec/interpretation/index.ts +39 -1
  68. package/test/unit/spec/locus-info/controlsUtils.js +8 -0
  69. package/test/unit/spec/meeting/index.js +200 -108
  70. package/test/unit/spec/meeting/locusMediaRequest.ts +96 -58
  71. package/test/unit/spec/meeting/utils.js +55 -0
  72. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  73. package/test/unit/spec/meetings/index.js +78 -1
  74. package/test/unit/spec/member/index.js +7 -0
  75. package/test/unit/spec/member/util.js +24 -0
  76. package/test/unit/spec/reachability/clusterReachability.ts +47 -1
  77. package/test/unit/spec/reachability/index.ts +12 -0
  78. package/test/unit/spec/reachability/request.js +47 -2
@@ -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
  /**
@@ -45,6 +45,9 @@ class ReachabilityRequest {
45
45
  joinCookie: any;
46
46
  discoveryOptions?: Record<string, any>;
47
47
  }> => {
48
+ const appType = this.webex?.config?.support?.appType;
49
+ const appVersion = this.webex?.config?.support?.appVersion;
50
+
48
51
  // we only measure latency for the initial startup call, not for other triggers
49
52
  const callWrapper =
50
53
  trigger === 'startup'
@@ -67,6 +70,10 @@ class ReachabilityRequest {
67
70
  'early-call-min-clusters': true,
68
71
  },
69
72
  'previous-report': previousReport,
73
+ ...(appType &&
74
+ appVersion && {
75
+ 'client-environment': {components: {[appType]: appVersion}},
76
+ }),
70
77
  trigger,
71
78
  },
72
79
  timeout: this.webex.config.meetings.reachabilityGetClusterTimeout,
@@ -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();
@@ -22,16 +22,54 @@ describe('plugin-meetings', () => {
22
22
  });
23
23
 
24
24
  describe('#initialize', () => {
25
+ beforeEach(() => {
26
+ interpretation.querySupportLanguages = sinon.stub();
27
+ interpretation.set({
28
+ canManageInterpreters: undefined,
29
+ hostSIEnabled: undefined,
30
+ locusUrl: undefined
31
+ });
32
+ });
33
+
34
+ afterEach(() => {
35
+ interpretation.querySupportLanguages.reset();
36
+ });
37
+
25
38
  it('creates SimultaneousInterpretation as expected', () => {
26
39
  assert.equal(interpretation.namespace, 'Meetings');
27
40
  });
28
41
  it('call querySupportLanguages correctly when meet the conditions', () => {
29
- interpretation.querySupportLanguages = sinon.stub();
30
42
  interpretation.set({
31
43
  canManageInterpreters: true,
44
+ hostSIEnabled: true,
45
+ locusUrl: "MOCK_LOCUS_URL"
32
46
  });
33
47
  assert.called(interpretation.querySupportLanguages);
34
48
  });
49
+
50
+ it('does not call querySupportLanguages when canManageInterpreters is not set', () => {
51
+ interpretation.set({
52
+ hostSIEnabled: true,
53
+ locusUrl: "MOCK_LOCUS_URL"
54
+ });
55
+ assert.notCalled(interpretation.querySupportLanguages);
56
+ });
57
+
58
+ it('does not call querySupportLanguages when hostSIEnabled is not set', () => {
59
+ interpretation.set({
60
+ canManageInterpreters: true,
61
+ locusUrl: "MOCK_LOCUS_URL"
62
+ });
63
+ assert.notCalled(interpretation.querySupportLanguages);
64
+ });
65
+
66
+ it('does not call querySupportLanguages when locusUrl is not set', () => {
67
+ interpretation.set({
68
+ canManageInterpreters: true,
69
+ hostSIEnabled: true,
70
+ });
71
+ assert.notCalled(interpretation.querySupportLanguages);
72
+ });
35
73
  });
36
74
 
37
75
  describe('#cleanUp', () => {
@@ -269,6 +269,14 @@ describe('plugin-meetings', () => {
269
269
  assert.equal(updates.hasPracticeSessionEnabledChanged, true);
270
270
  });
271
271
 
272
+ it('returns hasPracticeSessionEnabledChanged = false when enabled is false and previous state is false', () => {
273
+ const newControls = {practiceSession: {enabled: false}};
274
+
275
+ const {updates} = ControlsUtils.getControls(defaultControls, newControls);
276
+
277
+ assert.equal(updates.hasPracticeSessionEnabledChanged, false);
278
+ });
279
+
272
280
  it('returns hasEntryExitToneChanged = true when mode changed', () => {
273
281
  const newControls = {
274
282
  entryExitTone: {