@webex/plugin-meetings 3.8.0-next.4 → 3.8.0-next.41

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 (130) 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 +14 -1
  6. package/dist/constants.js.map +1 -1
  7. package/dist/controls-options-manager/enums.js +2 -0
  8. package/dist/controls-options-manager/enums.js.map +1 -1
  9. package/dist/controls-options-manager/types.js.map +1 -1
  10. package/dist/controls-options-manager/util.js +52 -0
  11. package/dist/controls-options-manager/util.js.map +1 -1
  12. package/dist/interpretation/index.js +1 -1
  13. package/dist/interpretation/siLanguage.js +1 -1
  14. package/dist/locus-info/controlsUtils.js +28 -10
  15. package/dist/locus-info/controlsUtils.js.map +1 -1
  16. package/dist/locus-info/index.js +20 -1
  17. package/dist/locus-info/index.js.map +1 -1
  18. package/dist/media/index.js +3 -15
  19. package/dist/media/index.js.map +1 -1
  20. package/dist/meeting/in-meeting-actions.js +11 -1
  21. package/dist/meeting/in-meeting-actions.js.map +1 -1
  22. package/dist/meeting/index.js +443 -256
  23. package/dist/meeting/index.js.map +1 -1
  24. package/dist/meeting/locusMediaRequest.js +21 -22
  25. package/dist/meeting/locusMediaRequest.js.map +1 -1
  26. package/dist/meeting/muteState.js +0 -2
  27. package/dist/meeting/muteState.js.map +1 -1
  28. package/dist/meeting/request.js +30 -0
  29. package/dist/meeting/request.js.map +1 -1
  30. package/dist/meeting/request.type.js.map +1 -1
  31. package/dist/meeting/util.js +10 -2
  32. package/dist/meeting/util.js.map +1 -1
  33. package/dist/meeting-info/meeting-info-v2.js +359 -60
  34. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  35. package/dist/meetings/index.js +60 -1
  36. package/dist/meetings/index.js.map +1 -1
  37. package/dist/member/index.js +10 -0
  38. package/dist/member/index.js.map +1 -1
  39. package/dist/member/util.js +3 -0
  40. package/dist/member/util.js.map +1 -1
  41. package/dist/metrics/constants.js +9 -0
  42. package/dist/metrics/constants.js.map +1 -1
  43. package/dist/reachability/clusterReachability.js +52 -8
  44. package/dist/reachability/clusterReachability.js.map +1 -1
  45. package/dist/reachability/index.js +70 -45
  46. package/dist/reachability/index.js.map +1 -1
  47. package/dist/reachability/reachability.types.js +14 -0
  48. package/dist/reachability/reachability.types.js.map +1 -1
  49. package/dist/reachability/request.js +19 -3
  50. package/dist/reachability/request.js.map +1 -1
  51. package/dist/reconnection-manager/index.js +2 -2
  52. package/dist/reconnection-manager/index.js.map +1 -1
  53. package/dist/recording-controller/util.js +5 -5
  54. package/dist/recording-controller/util.js.map +1 -1
  55. package/dist/roap/index.js.map +1 -1
  56. package/dist/roap/turnDiscovery.js +45 -27
  57. package/dist/roap/turnDiscovery.js.map +1 -1
  58. package/dist/roap/types.js +17 -0
  59. package/dist/roap/types.js.map +1 -0
  60. package/dist/types/config.d.ts +1 -0
  61. package/dist/types/constants.d.ts +10 -0
  62. package/dist/types/controls-options-manager/enums.d.ts +3 -1
  63. package/dist/types/controls-options-manager/types.d.ts +7 -1
  64. package/dist/types/locus-info/index.d.ts +1 -0
  65. package/dist/types/meeting/in-meeting-actions.d.ts +10 -0
  66. package/dist/types/meeting/index.d.ts +47 -1
  67. package/dist/types/meeting/muteState.d.ts +0 -1
  68. package/dist/types/meeting/request.d.ts +12 -1
  69. package/dist/types/meeting/request.type.d.ts +6 -0
  70. package/dist/types/meeting/util.d.ts +2 -1
  71. package/dist/types/meeting-info/meeting-info-v2.d.ts +80 -0
  72. package/dist/types/meetings/index.d.ts +29 -0
  73. package/dist/types/member/index.d.ts +1 -0
  74. package/dist/types/metrics/constants.d.ts +9 -0
  75. package/dist/types/reachability/clusterReachability.d.ts +13 -1
  76. package/dist/types/reachability/index.d.ts +2 -1
  77. package/dist/types/reachability/reachability.types.d.ts +5 -0
  78. package/dist/types/roap/index.d.ts +3 -2
  79. package/dist/types/roap/turnDiscovery.d.ts +5 -17
  80. package/dist/types/roap/types.d.ts +16 -0
  81. package/dist/webinar/index.js +1 -1
  82. package/package.json +22 -22
  83. package/src/config.ts +1 -0
  84. package/src/constants.ts +17 -0
  85. package/src/controls-options-manager/enums.ts +2 -0
  86. package/src/controls-options-manager/types.ts +11 -1
  87. package/src/controls-options-manager/util.ts +62 -0
  88. package/src/locus-info/controlsUtils.ts +44 -14
  89. package/src/locus-info/index.ts +23 -1
  90. package/src/media/index.ts +5 -21
  91. package/src/meeting/in-meeting-actions.ts +20 -0
  92. package/src/meeting/index.ts +263 -69
  93. package/src/meeting/locusMediaRequest.ts +27 -22
  94. package/src/meeting/muteState.ts +0 -2
  95. package/src/meeting/request.ts +36 -1
  96. package/src/meeting/request.type.ts +7 -0
  97. package/src/meeting/util.ts +9 -2
  98. package/src/meeting-info/meeting-info-v2.ts +247 -6
  99. package/src/meetings/index.ts +72 -1
  100. package/src/member/index.ts +11 -0
  101. package/src/member/util.ts +3 -0
  102. package/src/metrics/constants.ts +9 -0
  103. package/src/reachability/clusterReachability.ts +47 -1
  104. package/src/reachability/index.ts +15 -0
  105. package/src/reachability/reachability.types.ts +6 -0
  106. package/src/reachability/request.ts +7 -0
  107. package/src/reconnection-manager/index.ts +2 -2
  108. package/src/recording-controller/util.ts +17 -13
  109. package/src/roap/index.ts +3 -7
  110. package/src/roap/turnDiscovery.ts +34 -39
  111. package/src/roap/types.ts +23 -0
  112. package/test/unit/spec/controls-options-manager/util.js +120 -0
  113. package/test/unit/spec/locus-info/controlsUtils.js +103 -9
  114. package/test/unit/spec/locus-info/index.js +28 -0
  115. package/test/unit/spec/media/index.ts +6 -16
  116. package/test/unit/spec/meeting/in-meeting-actions.ts +13 -4
  117. package/test/unit/spec/meeting/index.js +490 -130
  118. package/test/unit/spec/meeting/locusMediaRequest.ts +95 -87
  119. package/test/unit/spec/meeting/muteState.js +0 -2
  120. package/test/unit/spec/meeting/request.js +32 -1
  121. package/test/unit/spec/meeting/utils.js +115 -18
  122. package/test/unit/spec/meeting-info/meetinginfov2.js +443 -114
  123. package/test/unit/spec/meetings/index.js +78 -1
  124. package/test/unit/spec/member/index.js +7 -0
  125. package/test/unit/spec/member/util.js +24 -0
  126. package/test/unit/spec/reachability/clusterReachability.ts +47 -1
  127. package/test/unit/spec/reachability/index.ts +12 -0
  128. package/test/unit/spec/reachability/request.js +47 -2
  129. package/test/unit/spec/reconnection-manager/index.js +4 -4
  130. package/test/unit/spec/roap/turnDiscovery.ts +110 -28
@@ -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,
@@ -591,9 +591,9 @@ export default class ReconnectionManager {
591
591
 
592
592
  const iceServers = [];
593
593
 
594
- if (turnServerResult.turnServerInfo?.url) {
594
+ if (turnServerResult.turnServerInfo?.urls.length > 0) {
595
595
  iceServers.push({
596
- urls: turnServerResult.turnServerInfo.url,
596
+ urls: turnServerResult.turnServerInfo.urls,
597
597
  username: turnServerResult.turnServerInfo.username || '',
598
598
  credential: turnServerResult.turnServerInfo.password || '',
599
599
  });
@@ -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();
package/src/roap/index.ts CHANGED
@@ -5,17 +5,13 @@ import {ROAP} from '../constants';
5
5
  import LoggerProxy from '../common/logs/logger-proxy';
6
6
 
7
7
  import RoapRequest from './request';
8
- import TurnDiscovery, {TurnDiscoveryResult} from './turnDiscovery';
8
+ import TurnDiscovery from './turnDiscovery';
9
+ import {TurnDiscoveryResult} from './types';
9
10
  import Meeting from '../meeting';
10
- import MeetingUtil from '../meeting/util';
11
11
  import Metrics from '../metrics';
12
12
  import BEHAVIORAL_METRICS from '../metrics/constants';
13
13
 
14
- export {
15
- type TurnDiscoveryResult,
16
- type TurnServerInfo,
17
- type TurnDiscoverySkipReason,
18
- } from './turnDiscovery';
14
+ export {type TurnDiscoveryResult, type TurnServerInfo, type TurnDiscoverySkipReason} from './types';
19
15
 
20
16
  /**
21
17
  * Roap options
@@ -4,11 +4,11 @@ import {Defer} from '@webex/common';
4
4
  import Metrics from '../metrics';
5
5
  import BEHAVIORAL_METRICS from '../metrics/constants';
6
6
  import LoggerProxy from '../common/logs/logger-proxy';
7
- import {ROAP, Enum} from '../constants';
7
+ import {ROAP} from '../constants';
8
8
 
9
9
  import RoapRequest from './request';
10
10
  import Meeting from '../meeting';
11
- import MeetingUtil from '../meeting/util';
11
+ import {TurnDiscoverySkipReason, TurnServerInfo, TurnDiscoveryResult} from './types';
12
12
 
13
13
  const TURN_DISCOVERY_TIMEOUT = 10; // in seconds
14
14
 
@@ -18,28 +18,6 @@ const TURN_DISCOVERY_TIMEOUT = 10; // in seconds
18
18
  // and do the SDP offer with seq=1
19
19
  const TURN_DISCOVERY_SEQ = 0;
20
20
 
21
- const TurnDiscoverySkipReason = {
22
- missingHttpResponse: 'missing http response', // when we asked for the TURN discovery response to be in the http response, but it wasn't there
23
- reachability: 'reachability', // when udp reachability to public clusters is ok, so we don't need TURN (this doens't apply when joinWithMedia() is used)
24
- alreadyInProgress: 'already in progress', // when we try to start TURN discovery while it's already in progress
25
- } as const;
26
-
27
- export type TurnDiscoverySkipReason =
28
- | Enum<typeof TurnDiscoverySkipReason> // this is a kind of FYI, because in practice typescript will infer the type of TurnDiscoverySkipReason as a string
29
- | string // used in case of errors, contains the error message
30
- | undefined; // used when TURN discovery is not skipped
31
-
32
- export type TurnServerInfo = {
33
- url: string;
34
- username: string;
35
- password: string;
36
- };
37
-
38
- export type TurnDiscoveryResult = {
39
- turnServerInfo?: TurnServerInfo;
40
- turnDiscoverySkippedReason: TurnDiscoverySkipReason;
41
- };
42
-
43
21
  /**
44
22
  * Handles the process of finding out TURN server information from Linus.
45
23
  * This is achieved by sending a TURN_DISCOVERY_REQUEST.
@@ -53,6 +31,17 @@ export default class TurnDiscovery {
53
31
 
54
32
  private responseTimer?: ReturnType<typeof setTimeout>;
55
33
 
34
+ /** Resets the turnInfo structure to the defaults
35
+ * @returns {void}
36
+ */
37
+ private resetTurnInfo() {
38
+ this.turnInfo = {
39
+ urls: [],
40
+ username: '',
41
+ password: '',
42
+ };
43
+ }
44
+
56
45
  /**
57
46
  * Constructor
58
47
  *
@@ -60,11 +49,7 @@ export default class TurnDiscovery {
60
49
  */
61
50
  constructor(roapRequest: RoapRequest) {
62
51
  this.roapRequest = roapRequest;
63
- this.turnInfo = {
64
- url: '',
65
- username: '',
66
- password: '',
67
- };
52
+ this.resetTurnInfo();
68
53
  }
69
54
 
70
55
  /**
@@ -134,21 +119,29 @@ export default class TurnDiscovery {
134
119
  }
135
120
 
136
121
  const expectedHeaders = [
137
- {headerName: 'x-cisco-turn-url', field: 'url'},
138
- {headerName: 'x-cisco-turn-username', field: 'username'},
139
- {headerName: 'x-cisco-turn-password', field: 'password'},
122
+ {headerName: 'x-cisco-turn-url', field: 'urls', multipleAllowed: true},
123
+ {headerName: 'x-cisco-turn-username', field: 'username', multipleAllowed: false},
124
+ {headerName: 'x-cisco-turn-password', field: 'password', multipleAllowed: false},
140
125
  ];
141
126
 
142
- let foundHeaders = 0;
127
+ const foundHeaders = {};
128
+
129
+ this.resetTurnInfo();
143
130
 
144
131
  headers?.forEach((receivedHeader) => {
145
132
  // check if it matches any of our expected headers
146
133
  expectedHeaders.forEach((expectedHeader) => {
147
134
  if (receivedHeader.startsWith(`${expectedHeader.headerName}=`)) {
148
- this.turnInfo[expectedHeader.field] = receivedHeader.substring(
149
- expectedHeader.headerName.length + 1
150
- );
151
- foundHeaders += 1;
135
+ foundHeaders[expectedHeader.headerName] = true;
136
+
137
+ const headerValue = receivedHeader.substring(expectedHeader.headerName.length + 1);
138
+
139
+ if (expectedHeader.multipleAllowed) {
140
+ this.turnInfo[expectedHeader.field].push(headerValue);
141
+ } else {
142
+ // just store the last one we find
143
+ this.turnInfo[expectedHeader.field] = headerValue;
144
+ }
152
145
  }
153
146
  });
154
147
  });
@@ -156,7 +149,7 @@ export default class TurnDiscovery {
156
149
  clearTimeout(this.responseTimer);
157
150
  this.responseTimer = undefined;
158
151
 
159
- if (foundHeaders !== expectedHeaders.length) {
152
+ if (expectedHeaders.some((header) => !foundHeaders[header.headerName])) {
160
153
  LoggerProxy.logger.warn(
161
154
  `Roap:turnDiscovery#handleTurnDiscoveryResponse --> missing some headers, received ${from}: ${JSON.stringify(
162
155
  headers
@@ -169,9 +162,11 @@ export default class TurnDiscovery {
169
162
  );
170
163
  } else {
171
164
  LoggerProxy.logger.info(
172
- `Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response ${from}, url=${this.turnInfo.url}`
165
+ `Roap:turnDiscovery#handleTurnDiscoveryResponse --> received a valid response ${from}, urls=${this.turnInfo.urls}`
173
166
  );
174
167
 
168
+ this.turnInfo.urls = this.turnInfo.urls.filter((url) => url !== ''); // remove empty urls, we might get them if we land on video-mesh nodes (VMN)
169
+
175
170
  this.defer.resolve({isOkRequired: !headers?.includes('noOkInTransaction')});
176
171
  }
177
172
  }
@@ -0,0 +1,23 @@
1
+ import {Enum} from '../constants';
2
+
3
+ export const TurnDiscoverySkipReason = {
4
+ missingHttpResponse: 'missing http response', // when we asked for the TURN discovery response to be in the http response, but it wasn't there
5
+ reachability: 'reachability', // when udp reachability to public clusters is ok, so we don't need TURN (this doens't apply when joinWithMedia() is used)
6
+ alreadyInProgress: 'already in progress', // when we try to start TURN discovery while it's already in progress
7
+ } as const;
8
+
9
+ export type TurnDiscoverySkipReason =
10
+ | Enum<typeof TurnDiscoverySkipReason> // this is a kind of FYI, because in practice typescript will infer the type of TurnDiscoverySkipReason as a string
11
+ | string // used in case of errors, contains the error message
12
+ | undefined; // used when TURN discovery is not skipped
13
+
14
+ export type TurnServerInfo = {
15
+ urls: string[];
16
+ username: string;
17
+ password: string;
18
+ };
19
+
20
+ export type TurnDiscoveryResult = {
21
+ turnServerInfo?: TurnServerInfo;
22
+ turnDiscoverySkippedReason: TurnDiscoverySkipReason;
23
+ };
@@ -406,6 +406,74 @@ describe('plugin-meetings', () => {
406
406
  });
407
407
  });
408
408
 
409
+ describe('canUpdateAnnotation()', () => {
410
+ beforeEach(() => {
411
+ sinon.stub(ControlsOptionsUtil, 'hasHints').returns(true);
412
+ });
413
+
414
+ it('should call hasHints() with proper hints when `enabled` is true', () => {
415
+ ControlsOptionsUtil.canUpdateAnnotation({properties: {enabled: true}}, []);
416
+
417
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
418
+ requiredHints: [DISPLAY_HINTS.ENABLE_ANNOTATION_MEETING_OPTION],
419
+ displayHints: [],
420
+ });
421
+ });
422
+
423
+ it('should call hasHints() with proper hints when `enabled` is false', () => {
424
+ ControlsOptionsUtil.canUpdateAnnotation({properties: {enabled: false}}, []);
425
+
426
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
427
+ requiredHints: [DISPLAY_HINTS.DISABLE_ANNOTATION_MEETING_OPTION],
428
+ displayHints: [],
429
+ });
430
+ });
431
+
432
+ it('should return the resolution of hasHints()', () => {
433
+ const expected = 'example-return-value';
434
+ ControlsOptionsUtil.hasHints.returns(expected);
435
+
436
+ const results = ControlsOptionsUtil.canUpdateAnnotation({properties: {}}, []);
437
+
438
+ assert.calledOnce(ControlsOptionsUtil.hasHints);
439
+ assert.equal(results, expected);
440
+ });
441
+ });
442
+
443
+ describe('canUpdateRemoteDesktopControl()', () => {
444
+ beforeEach(() => {
445
+ sinon.stub(ControlsOptionsUtil, 'hasHints').returns(true);
446
+ });
447
+
448
+ it('should call hasHints() with proper hints when `enabled` is true', () => {
449
+ ControlsOptionsUtil.canUpdateRemoteDesktopControl({properties: {enabled: true}}, []);
450
+
451
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
452
+ requiredHints: [DISPLAY_HINTS.ENABLE_RDC_MEETING_OPTION],
453
+ displayHints: [],
454
+ });
455
+ });
456
+
457
+ it('should call hasHints() with proper hints when `enabled` is false', () => {
458
+ ControlsOptionsUtil.canUpdateRemoteDesktopControl({properties: {enabled: false}}, []);
459
+
460
+ assert.calledWith(ControlsOptionsUtil.hasHints, {
461
+ requiredHints: [DISPLAY_HINTS.DISABLE_RDC_MEETING_OPTION],
462
+ displayHints: [],
463
+ });
464
+ });
465
+
466
+ it('should return the resolution of hasHints()', () => {
467
+ const expected = 'example-return-value';
468
+ ControlsOptionsUtil.hasHints.returns(expected);
469
+
470
+ const results = ControlsOptionsUtil.canUpdateRemoteDesktopControl({properties: {}}, []);
471
+
472
+ assert.calledOnce(ControlsOptionsUtil.hasHints);
473
+ assert.equal(results, expected);
474
+ });
475
+ });
476
+
409
477
  describe('canUpdate()', () => {
410
478
  const displayHints = [];
411
479
 
@@ -416,6 +484,8 @@ describe('plugin-meetings', () => {
416
484
  ControlsOptionsUtil.canUpdateShareControl = sinon.stub().returns(true);
417
485
  ControlsOptionsUtil.canUpdateVideo = sinon.stub().returns(true);
418
486
  ControlsOptionsUtil.canUpdateViewTheParticipantsList = sinon.stub().returns(true);
487
+ ControlsOptionsUtil.canUpdateAnnotation = sinon.stub().returns(true);
488
+ ControlsOptionsUtil.canUpdateRemoteDesktopControl = sinon.stub().returns(true);
419
489
  });
420
490
 
421
491
  it('should only call canUpdateAudio() if the scope is audio', () => {
@@ -429,6 +499,8 @@ describe('plugin-meetings', () => {
429
499
  assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
430
500
  assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
431
501
  assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
502
+ assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
503
+ assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
432
504
  assert.isTrue(results);
433
505
  });
434
506
 
@@ -443,6 +515,8 @@ describe('plugin-meetings', () => {
443
515
  assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
444
516
  assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
445
517
  assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
518
+ assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
519
+ assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
446
520
  assert.isTrue(results);
447
521
  });
448
522
 
@@ -457,6 +531,8 @@ describe('plugin-meetings', () => {
457
531
  assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
458
532
  assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
459
533
  assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
534
+ assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
535
+ assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
460
536
  assert.isTrue(results);
461
537
  });
462
538
 
@@ -471,6 +547,8 @@ describe('plugin-meetings', () => {
471
547
  assert.calledWith(ControlsOptionsUtil.canUpdateShareControl, displayHints);
472
548
  assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
473
549
  assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
550
+ assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
551
+ assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
474
552
  assert.isTrue(results);
475
553
  });
476
554
 
@@ -485,6 +563,8 @@ describe('plugin-meetings', () => {
485
563
  assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
486
564
  assert.calledWith(ControlsOptionsUtil.canUpdateVideo, control, displayHints);
487
565
  assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
566
+ assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
567
+ assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
488
568
  assert.isTrue(results);
489
569
  });
490
570
 
@@ -503,6 +583,44 @@ describe('plugin-meetings', () => {
503
583
  control,
504
584
  displayHints
505
585
  );
586
+ assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
587
+ assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
588
+ assert.isTrue(results);
589
+ });
590
+
591
+ it('should only call canUpdateAnnotation() if the scope is annotation', () => {
592
+ const control = {scope: 'annotation'};
593
+
594
+ const results = ControlsOptionsUtil.canUpdate(control, displayHints);
595
+
596
+ assert.callCount(ControlsOptionsUtil.canUpdateAudio, 0);
597
+ assert.callCount(ControlsOptionsUtil.canUpdateRaiseHand, 0);
598
+ assert.callCount(ControlsOptionsUtil.canUpdateReactions, 0);
599
+ assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
600
+ assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
601
+ assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
602
+ assert.calledWith(ControlsOptionsUtil.canUpdateAnnotation, control, displayHints);
603
+ assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
604
+ assert.isTrue(results);
605
+ });
606
+
607
+ it('should only call canUpdateRemoteDesktopControl() if the scope is rdc', () => {
608
+ const control = {scope: 'rdc'};
609
+
610
+ const results = ControlsOptionsUtil.canUpdate(control, displayHints);
611
+
612
+ assert.callCount(ControlsOptionsUtil.canUpdateAudio, 0);
613
+ assert.callCount(ControlsOptionsUtil.canUpdateRaiseHand, 0);
614
+ assert.callCount(ControlsOptionsUtil.canUpdateReactions, 0);
615
+ assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
616
+ assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
617
+ assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
618
+ assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
619
+ assert.calledWith(
620
+ ControlsOptionsUtil.canUpdateRemoteDesktopControl,
621
+ control,
622
+ displayHints
623
+ );
506
624
  assert.isTrue(results);
507
625
  });
508
626
 
@@ -517,6 +635,8 @@ describe('plugin-meetings', () => {
517
635
  assert.callCount(ControlsOptionsUtil.canUpdateShareControl, 0);
518
636
  assert.callCount(ControlsOptionsUtil.canUpdateVideo, 0);
519
637
  assert.callCount(ControlsOptionsUtil.canUpdateViewTheParticipantsList, 0);
638
+ assert.callCount(ControlsOptionsUtil.canUpdateAnnotation, 0);
639
+ assert.callCount(ControlsOptionsUtil.canUpdateRemoteDesktopControl, 0);
520
640
  assert.isFalse(results);
521
641
  });
522
642
  });