@webex/plugin-meetings 3.10.0-next.27 → 3.10.0-next.29

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 (46) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/config.js +2 -1
  4. package/dist/config.js.map +1 -1
  5. package/dist/hashTree/hashTreeParser.js +3 -14
  6. package/dist/hashTree/hashTreeParser.js.map +1 -1
  7. package/dist/hashTree/types.js.map +1 -1
  8. package/dist/hashTree/utils.js +11 -0
  9. package/dist/hashTree/utils.js.map +1 -1
  10. package/dist/interpretation/index.js +1 -1
  11. package/dist/interpretation/siLanguage.js +1 -1
  12. package/dist/locus-info/index.js +2 -1
  13. package/dist/locus-info/index.js.map +1 -1
  14. package/dist/meetings/index.js +25 -11
  15. package/dist/meetings/index.js.map +1 -1
  16. package/dist/meetings/util.js +11 -7
  17. package/dist/meetings/util.js.map +1 -1
  18. package/dist/reachability/clusterReachability.js +171 -18
  19. package/dist/reachability/clusterReachability.js.map +1 -1
  20. package/dist/reachability/index.js +3 -1
  21. package/dist/reachability/index.js.map +1 -1
  22. package/dist/reachability/reachabilityPeerConnection.js +1 -1
  23. package/dist/reachability/reachabilityPeerConnection.js.map +1 -1
  24. package/dist/types/config.d.ts +1 -0
  25. package/dist/types/hashTree/hashTreeParser.d.ts +1 -11
  26. package/dist/types/hashTree/types.d.ts +4 -0
  27. package/dist/types/hashTree/utils.d.ts +7 -0
  28. package/dist/types/locus-info/index.d.ts +2 -1
  29. package/dist/types/reachability/clusterReachability.d.ts +30 -3
  30. package/dist/webinar/index.js +1 -1
  31. package/package.json +1 -1
  32. package/src/config.ts +1 -0
  33. package/src/hashTree/hashTreeParser.ts +2 -16
  34. package/src/hashTree/types.ts +5 -0
  35. package/src/hashTree/utils.ts +11 -0
  36. package/src/locus-info/index.ts +2 -3
  37. package/src/meetings/index.ts +24 -17
  38. package/src/meetings/util.ts +10 -9
  39. package/src/reachability/clusterReachability.ts +153 -27
  40. package/src/reachability/index.ts +6 -1
  41. package/src/reachability/reachabilityPeerConnection.ts +3 -1
  42. package/test/unit/spec/hashTree/utils.ts +38 -1
  43. package/test/unit/spec/meetings/index.js +192 -1
  44. package/test/unit/spec/meetings/utils.js +51 -1
  45. package/test/unit/spec/reachability/clusterReachability.ts +125 -1
  46. package/test/unit/spec/reachability/index.ts +3 -3
@@ -23,26 +23,53 @@ export declare const Events: {
23
23
  export type Events = Enum<typeof Events>;
24
24
  /**
25
25
  * A class that handles reachability checks for a single cluster.
26
- * Creates and orchestrates a ReachabilityPeerConnection instance.
26
+ * Creates and orchestrates ReachabilityPeerConnection instance(s).
27
27
  * Listens to events and emits them to consumers.
28
+ *
29
+ * When enablePerUdpUrlReachability is true:
30
+ * - Creates one ReachabilityPeerConnection for each UDP URL
31
+ * - Creates one ReachabilityPeerConnection for all TCP and TLS URLs together
32
+ * Otherwise:
33
+ * - Creates a single ReachabilityPeerConnection for all URLs
28
34
  */
29
35
  export declare class ClusterReachability extends EventsScope {
30
36
  private reachabilityPeerConnection;
37
+ private reachabilityPeerConnectionsForUdp;
31
38
  readonly isVideoMesh: boolean;
32
39
  readonly name: any;
33
40
  readonly reachedSubnets: Set<string>;
41
+ private enablePerUdpUrlReachability;
42
+ private udpResultEmitted;
34
43
  /**
35
44
  * Constructor for ClusterReachability
36
45
  * @param {string} name cluster name
37
46
  * @param {ClusterNode} clusterInfo information about the media cluster
47
+ * @param {boolean} enablePerUdpUrlReachability whether to create separate peer connections per UDP URL
38
48
  */
39
- constructor(name: string, clusterInfo: ClusterNode);
49
+ constructor(name: string, clusterInfo: ClusterNode, enablePerUdpUrlReachability?: boolean);
40
50
  /**
41
- * Sets up event listeners for the ReachabilityPeerConnection instance
51
+ * Initializes a single ReachabilityPeerConnection for all protocols
52
+ * @param {ClusterNode} clusterInfo information about the media cluster
53
+ * @returns {void}
54
+ */
55
+ private initializeSingleReachabilityPeerConnection;
56
+ /**
57
+ * Initializes per-URL UDP reachability checks:
58
+ * - One ReachabilityPeerConnection per UDP URL
59
+ * - One ReachabilityPeerConnection for all TCP and TLS URLs together
60
+ * @param {ClusterNode} clusterInfo information about the media cluster
61
+ * @returns {void}
62
+ */
63
+ private initializePerUdpUrlReachabilityCheck;
64
+ /**
65
+ * Sets up event listeners for a ReachabilityPeerConnection instance
66
+ * @param {ReachabilityPeerConnection} rpc the ReachabilityPeerConnection instance
67
+ * @param {boolean} isUdpPerUrl whether this is a per-URL UDP instance
42
68
  * @returns {void}
43
69
  */
44
70
  private setupReachabilityPeerConnectionEventListeners;
45
71
  /**
72
+ * Gets the aggregated reachability result for this cluster.
46
73
  * @returns {ClusterReachabilityResult} reachability result for this cluster
47
74
  */
48
75
  getResult(): ClusterReachabilityResult;
@@ -448,7 +448,7 @@ var Webinar = _webexCore.WebexPlugin.extend({
448
448
  }, _callee7);
449
449
  }))();
450
450
  },
451
- version: "3.10.0-next.27"
451
+ version: "3.10.0-next.29"
452
452
  });
453
453
  var _default = exports.default = Webinar;
454
454
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -93,5 +93,5 @@
93
93
  "//": [
94
94
  "TODO: upgrade jwt-decode when moving to node 18"
95
95
  ],
96
- "version": "3.10.0-next.27"
96
+ "version": "3.10.0-next.29"
97
97
  }
package/src/config.ts CHANGED
@@ -100,5 +100,6 @@ export default {
100
100
  logUploadIntervalMultiplicationFactor: 0, // if set to 0 or undefined, logs won't be uploaded periodically, if you want periodic logs, recommended value is 1
101
101
  stopIceGatheringAfterFirstRelayCandidate: false,
102
102
  enableAudioTwccForMultistream: false,
103
+ enablePerUdpUrlReachability: false, // true: separate peer connection per each UDP URL; false: single peer connection for all URLs
103
104
  },
104
105
  };
@@ -3,9 +3,9 @@ import HashTree, {LeafDataItem} from './hashTree';
3
3
  import LoggerProxy from '../common/logs/logger-proxy';
4
4
  import {Enum, HTTP_VERBS} from '../constants';
5
5
  import {DataSetNames, EMPTY_HASH} from './constants';
6
- import {ObjectType, HtMeta} from './types';
6
+ import {ObjectType, HtMeta, HashTreeObject} from './types';
7
7
  import {LocusDTO} from '../locus-info/types';
8
- import {deleteNestedObjectsWithHtMeta} from './utils';
8
+ import {deleteNestedObjectsWithHtMeta, isSelf} from './utils';
9
9
 
10
10
  export interface DataSet {
11
11
  url: string;
@@ -20,11 +20,6 @@ export interface DataSet {
20
20
  };
21
21
  }
22
22
 
23
- export interface HashTreeObject {
24
- htMeta: HtMeta;
25
- data: Record<string, any>;
26
- }
27
-
28
23
  export interface RootHashMessage {
29
24
  dataSets: Array<DataSet>;
30
25
  }
@@ -60,15 +55,6 @@ export type LocusInfoUpdateCallback = (
60
55
  */
61
56
  class MeetingEndedError extends Error {}
62
57
 
63
- /**
64
- * Checks if the given hash tree object is of type "self"
65
- * @param {HashTreeObject} object object to check
66
- * @returns {boolean} True if the object is of type "self", false otherwise
67
- */
68
- export function isSelf(object: HashTreeObject) {
69
- return object.htMeta.elementId.type.toLowerCase() === ObjectType.self;
70
- }
71
-
72
58
  /**
73
59
  * Parses hash tree eventing locus data
74
60
  */
@@ -32,3 +32,8 @@ export interface HtMeta {
32
32
  };
33
33
  dataSetNames: string[];
34
34
  }
35
+
36
+ export interface HashTreeObject {
37
+ htMeta: HtMeta;
38
+ data: Record<string, any>;
39
+ }
@@ -1,5 +1,16 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
2
 
3
+ import {ObjectType, HashTreeObject} from './types';
4
+
5
+ /**
6
+ * Checks if the given hash tree object is of type "self"
7
+ * @param {HashTreeObject} object object to check
8
+ * @returns {boolean} True if the object is of type "self", false otherwise
9
+ */
10
+ export function isSelf(object: HashTreeObject) {
11
+ return object.htMeta.elementId.type.toLowerCase() === ObjectType.self;
12
+ }
13
+
3
14
  /**
4
15
  * Analyzes given part of Locus DTO recursively and delete any nested objects that have their own htMeta
5
16
  *
@@ -34,11 +34,10 @@ import BEHAVIORAL_METRICS from '../metrics/constants';
34
34
  import HashTreeParser, {
35
35
  DataSet,
36
36
  HashTreeMessage,
37
- HashTreeObject,
38
- isSelf,
39
37
  LocusInfoUpdateType,
40
38
  } from '../hashTree/hashTreeParser';
41
- import {ObjectType, ObjectTypeToLocusKeyMap} from '../hashTree/types';
39
+ import {HashTreeObject, ObjectType, ObjectTypeToLocusKeyMap} from '../hashTree/types';
40
+ import {isSelf} from '../hashTree/utils';
42
41
  import {Links, LocusDTO, LocusFullState} from './types';
43
42
 
44
43
  export type LocusLLMEvent = {
@@ -67,6 +67,8 @@ import {SpaceIDDeprecatedError} from '../common/errors/webex-errors';
67
67
  import NoMeetingInfoError from '../common/errors/no-meeting-info';
68
68
  import JoinForbiddenError from '../common/errors/join-forbidden-error';
69
69
  import {HashTreeMessage} from '../hashTree/hashTreeParser';
70
+ import {HashTreeObject} from '../hashTree/types';
71
+ import {isSelf} from '../hashTree/utils';
70
72
 
71
73
  let mediaLogger;
72
74
 
@@ -420,31 +422,36 @@ export default class Meetings extends WebexPlugin {
420
422
  * @memberof Meetings
421
423
  */
422
424
  getCorrespondingMeetingByLocus(data: LocusEvent) {
423
- if (
424
- data.eventType === LOCUSEVENT.HASH_TREE_DATA_UPDATED &&
425
- data.stateElementsMessage?.locusUrl
426
- ) {
427
- return this.meetingCollection.getByKey(
428
- MEETING_KEY.LOCUS_URL,
429
- data.stateElementsMessage.locusUrl
430
- );
425
+ const locusUrl =
426
+ data.stateElementsMessage?.locusUrl || // hash tree event
427
+ data.locusUrl; // classic event
428
+
429
+ // first try to find by locusUrl - that's the simplest and quickest way
430
+ const existingMeeting = this.meetingCollection.getByKey(MEETING_KEY.LOCUS_URL, locusUrl);
431
+
432
+ if (existingMeeting) {
433
+ return existingMeeting;
431
434
  }
432
435
 
433
- // getting meeting by correlationId. This will happen for the new event
434
- // Either the locus
435
- // TODO : Add check for the callBack Address
436
+ // if that didn't work, fallback to other fields like correlationId, sipUri, etc
437
+
438
+ // If the event is a hash tree event, we need to extract "self" object from it
439
+ // We don't care about the version, just need to find the meeting this event is for,
440
+ // so any hash tree object of type "self" will do
441
+ const hashTreeEventSelf = data.stateElementsMessage?.locusStateElements?.find(
442
+ (obj: HashTreeObject) => isSelf(obj)
443
+ )?.data;
444
+
445
+ const self = hashTreeEventSelf || data.locus?.self;
446
+
436
447
  return (
437
- this.meetingCollection.getByKey(MEETING_KEY.LOCUS_URL, data.locusUrl) ||
438
448
  // @ts-ignore
439
449
  this.meetingCollection.getByKey(
440
450
  MEETING_KEY.CORRELATION_ID,
441
451
  // @ts-ignore
442
- MeetingsUtil.checkForCorrelationId(this.webex.internal.device.url, data.locus)
443
- ) ||
444
- this.meetingCollection.getByKey(
445
- MEETING_KEY.SIP_URI,
446
- data.locus?.self?.callbackInfo?.callbackAddress
452
+ MeetingsUtil.getCorrelationIdForDevice(this.webex.internal.device.url, self)
447
453
  ) ||
454
+ this.meetingCollection.getByKey(MEETING_KEY.SIP_URI, self?.callbackInfo?.callbackAddress) ||
448
455
  (data.locus?.info?.isUnifiedSpaceMeeting
449
456
  ? undefined
450
457
  : this.meetingCollection.getByKey(
@@ -117,15 +117,16 @@ MeetingsUtil.getMediaServerIp = (sdp) => {
117
117
  return mediaServerIp;
118
118
  };
119
119
 
120
- MeetingsUtil.checkForCorrelationId = (deviceUrl, locus) => {
121
- let devices = [];
122
-
123
- if (locus) {
124
- if (locus && locus.self && locus.self.devices) {
125
- devices = locus.self.devices;
126
- }
127
-
128
- const foundDevice = devices.find((device) => device.url === deviceUrl);
120
+ /**
121
+ * Finds correlationId of a device from locus self devices array
122
+ * that matches the given deviceUrl
123
+ * @param {string} deviceUrl
124
+ * @param {object} locusSelf
125
+ * @returns {string|false} correlationId or false if not found
126
+ */
127
+ MeetingsUtil.getCorrelationIdForDevice = (deviceUrl: string, locusSelf: any) => {
128
+ if (locusSelf?.devices) {
129
+ const foundDevice = locusSelf?.devices.find((device) => device.url === deviceUrl);
129
130
 
130
131
  if (foundDevice && foundDevice.correlationId) {
131
132
  return foundDevice.correlationId;
@@ -1,5 +1,6 @@
1
1
  import {ClusterNode} from './request';
2
2
  import EventsScope from '../common/events/events-scope';
3
+ import LoggerProxy from '../common/logs/logger-proxy';
3
4
 
4
5
  import {Enum} from '../constants';
5
6
  import {
@@ -37,36 +38,117 @@ export type Events = Enum<typeof Events>;
37
38
 
38
39
  /**
39
40
  * A class that handles reachability checks for a single cluster.
40
- * Creates and orchestrates a ReachabilityPeerConnection instance.
41
+ * Creates and orchestrates ReachabilityPeerConnection instance(s).
41
42
  * Listens to events and emits them to consumers.
43
+ *
44
+ * When enablePerUdpUrlReachability is true:
45
+ * - Creates one ReachabilityPeerConnection for each UDP URL
46
+ * - Creates one ReachabilityPeerConnection for all TCP and TLS URLs together
47
+ * Otherwise:
48
+ * - Creates a single ReachabilityPeerConnection for all URLs
42
49
  */
43
50
  export class ClusterReachability extends EventsScope {
44
- private reachabilityPeerConnection: ReachabilityPeerConnection;
51
+ private reachabilityPeerConnection: ReachabilityPeerConnection | null = null;
52
+ private reachabilityPeerConnectionsForUdp: ReachabilityPeerConnection[] = [];
53
+
45
54
  public readonly isVideoMesh: boolean;
46
55
  public readonly name;
47
56
  public readonly reachedSubnets: Set<string> = new Set();
48
57
 
58
+ private enablePerUdpUrlReachability: boolean;
59
+ private udpResultEmitted = false;
60
+
49
61
  /**
50
62
  * Constructor for ClusterReachability
51
63
  * @param {string} name cluster name
52
64
  * @param {ClusterNode} clusterInfo information about the media cluster
65
+ * @param {boolean} enablePerUdpUrlReachability whether to create separate peer connections per UDP URL
53
66
  */
54
- constructor(name: string, clusterInfo: ClusterNode) {
67
+ constructor(name: string, clusterInfo: ClusterNode, enablePerUdpUrlReachability = false) {
55
68
  super();
56
69
  this.name = name;
57
70
  this.isVideoMesh = clusterInfo.isVideoMesh;
71
+ this.enablePerUdpUrlReachability = enablePerUdpUrlReachability;
58
72
 
59
- this.reachabilityPeerConnection = new ReachabilityPeerConnection(name, clusterInfo);
73
+ if (this.enablePerUdpUrlReachability) {
74
+ this.initializePerUdpUrlReachabilityCheck(clusterInfo);
75
+ } else {
76
+ this.initializeSingleReachabilityPeerConnection(clusterInfo);
77
+ }
78
+ }
60
79
 
61
- this.setupReachabilityPeerConnectionEventListeners();
80
+ /**
81
+ * Initializes a single ReachabilityPeerConnection for all protocols
82
+ * @param {ClusterNode} clusterInfo information about the media cluster
83
+ * @returns {void}
84
+ */
85
+ private initializeSingleReachabilityPeerConnection(clusterInfo: ClusterNode) {
86
+ this.reachabilityPeerConnection = new ReachabilityPeerConnection(this.name, clusterInfo);
87
+ this.setupReachabilityPeerConnectionEventListeners(this.reachabilityPeerConnection);
62
88
  }
63
89
 
64
90
  /**
65
- * Sets up event listeners for the ReachabilityPeerConnection instance
91
+ * Initializes per-URL UDP reachability checks:
92
+ * - One ReachabilityPeerConnection per UDP URL
93
+ * - One ReachabilityPeerConnection for all TCP and TLS URLs together
94
+ * @param {ClusterNode} clusterInfo information about the media cluster
66
95
  * @returns {void}
67
96
  */
68
- private setupReachabilityPeerConnectionEventListeners() {
69
- this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.resultReady, (data) => {
97
+ private initializePerUdpUrlReachabilityCheck(clusterInfo: ClusterNode) {
98
+ LoggerProxy.logger.log(
99
+ `ClusterReachability#initializePerUdpUrlReachabilityCheck --> cluster: ${this.name}, performing per-URL UDP reachability for ${clusterInfo.udp.length} URLs`
100
+ );
101
+
102
+ // Create one ReachabilityPeerConnection for each UDP URL
103
+ clusterInfo.udp.forEach((udpUrl) => {
104
+ const singleUdpClusterInfo: ClusterNode = {
105
+ isVideoMesh: clusterInfo.isVideoMesh,
106
+ udp: [udpUrl],
107
+ tcp: [],
108
+ xtls: [],
109
+ };
110
+ const rpc = new ReachabilityPeerConnection(this.name, singleUdpClusterInfo);
111
+ this.setupReachabilityPeerConnectionEventListeners(rpc, true);
112
+ this.reachabilityPeerConnectionsForUdp.push(rpc);
113
+ });
114
+
115
+ // Create one ReachabilityPeerConnection for all TCP and TLS URLs together
116
+ if (clusterInfo.tcp.length > 0 || clusterInfo.xtls.length > 0) {
117
+ const tcpTlsClusterInfo: ClusterNode = {
118
+ isVideoMesh: clusterInfo.isVideoMesh,
119
+ udp: [],
120
+ tcp: clusterInfo.tcp,
121
+ xtls: clusterInfo.xtls,
122
+ };
123
+ this.reachabilityPeerConnection = new ReachabilityPeerConnection(
124
+ this.name,
125
+ tcpTlsClusterInfo
126
+ );
127
+ this.setupReachabilityPeerConnectionEventListeners(this.reachabilityPeerConnection);
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Sets up event listeners for a ReachabilityPeerConnection instance
133
+ * @param {ReachabilityPeerConnection} rpc the ReachabilityPeerConnection instance
134
+ * @param {boolean} isUdpPerUrl whether this is a per-URL UDP instance
135
+ * @returns {void}
136
+ */
137
+ private setupReachabilityPeerConnectionEventListeners(
138
+ rpc: ReachabilityPeerConnection,
139
+ isUdpPerUrl = false
140
+ ) {
141
+ rpc.on(ReachabilityPeerConnectionEvents.resultReady, (data) => {
142
+ // For per-URL UDP checks, only emit the first successful UDP result
143
+ if (isUdpPerUrl && data.protocol === 'udp') {
144
+ if (this.udpResultEmitted) {
145
+ return;
146
+ }
147
+ if (data.result === 'reachable') {
148
+ this.udpResultEmitted = true;
149
+ }
150
+ }
151
+
70
152
  this.emit(
71
153
  {
72
154
  file: 'clusterReachability',
@@ -77,21 +159,18 @@ export class ClusterReachability extends EventsScope {
77
159
  );
78
160
  });
79
161
 
80
- this.reachabilityPeerConnection.on(
81
- ReachabilityPeerConnectionEvents.clientMediaIpsUpdated,
82
- (data) => {
83
- this.emit(
84
- {
85
- file: 'clusterReachability',
86
- function: 'setupReachabilityPeerConnectionEventListeners',
87
- },
88
- Events.clientMediaIpsUpdated,
89
- data
90
- );
91
- }
92
- );
162
+ rpc.on(ReachabilityPeerConnectionEvents.clientMediaIpsUpdated, (data) => {
163
+ this.emit(
164
+ {
165
+ file: 'clusterReachability',
166
+ function: 'setupReachabilityPeerConnectionEventListeners',
167
+ },
168
+ Events.clientMediaIpsUpdated,
169
+ data
170
+ );
171
+ });
93
172
 
94
- this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.natTypeUpdated, (data) => {
173
+ rpc.on(ReachabilityPeerConnectionEvents.natTypeUpdated, (data) => {
95
174
  this.emit(
96
175
  {
97
176
  file: 'clusterReachability',
@@ -102,18 +181,54 @@ export class ClusterReachability extends EventsScope {
102
181
  );
103
182
  });
104
183
 
105
- this.reachabilityPeerConnection.on(ReachabilityPeerConnectionEvents.reachedSubnets, (data) => {
106
- data.subnets.forEach((subnet) => {
184
+ rpc.on(ReachabilityPeerConnectionEvents.reachedSubnets, (data) => {
185
+ data.subnets.forEach((subnet: string) => {
107
186
  this.reachedSubnets.add(subnet);
108
187
  });
109
188
  });
110
189
  }
111
190
 
112
191
  /**
192
+ * Gets the aggregated reachability result for this cluster.
113
193
  * @returns {ClusterReachabilityResult} reachability result for this cluster
114
194
  */
115
195
  getResult(): ClusterReachabilityResult {
116
- return this.reachabilityPeerConnection.getResult();
196
+ if (!this.enablePerUdpUrlReachability) {
197
+ return (
198
+ this.reachabilityPeerConnection?.getResult() ?? {
199
+ udp: {result: 'untested'},
200
+ tcp: {result: 'untested'},
201
+ xtls: {result: 'untested'},
202
+ }
203
+ );
204
+ }
205
+
206
+ const result: ClusterReachabilityResult = {
207
+ udp: {result: 'untested'},
208
+ tcp: {result: 'untested'},
209
+ xtls: {result: 'untested'},
210
+ };
211
+
212
+ // Get the first reachable UDP result from per-URL instances
213
+ for (const rpc of this.reachabilityPeerConnectionsForUdp) {
214
+ const rpcResult = rpc.getResult();
215
+ if (rpcResult.udp.result === 'reachable') {
216
+ result.udp = rpcResult.udp;
217
+ break;
218
+ }
219
+ if (rpcResult.udp.result === 'unreachable' && result.udp.result === 'untested') {
220
+ result.udp = rpcResult.udp;
221
+ }
222
+ }
223
+
224
+ // Get TCP and TLS results from the main peer connection
225
+ if (this.reachabilityPeerConnection) {
226
+ const mainResult = this.reachabilityPeerConnection.getResult();
227
+ result.tcp = mainResult.tcp;
228
+ result.xtls = mainResult.xtls;
229
+ }
230
+
231
+ return result;
117
232
  }
118
233
 
119
234
  /**
@@ -121,7 +236,17 @@ export class ClusterReachability extends EventsScope {
121
236
  * @returns {Promise<ClusterReachabilityResult>}
122
237
  */
123
238
  async start(): Promise<ClusterReachabilityResult> {
124
- await this.reachabilityPeerConnection.start();
239
+ const startPromises: Promise<ClusterReachabilityResult>[] = [];
240
+
241
+ this.reachabilityPeerConnectionsForUdp.forEach((rpc) => {
242
+ startPromises.push(rpc.start());
243
+ });
244
+
245
+ if (this.reachabilityPeerConnection) {
246
+ startPromises.push(this.reachabilityPeerConnection.start());
247
+ }
248
+
249
+ await Promise.all(startPromises);
125
250
 
126
251
  return this.getResult();
127
252
  }
@@ -131,6 +256,7 @@ export class ClusterReachability extends EventsScope {
131
256
  * @returns {void}
132
257
  */
133
258
  public abort() {
134
- this.reachabilityPeerConnection.abort();
259
+ this.reachabilityPeerConnectionsForUdp.forEach((rpc) => rpc.abort());
260
+ this.reachabilityPeerConnection?.abort();
135
261
  }
136
262
  }
@@ -961,7 +961,12 @@ export default class Reachability extends EventsScope {
961
961
  Object.keys(clusterList).forEach((key) => {
962
962
  const cluster = clusterList[key];
963
963
 
964
- this.clusterReachability[key] = new ClusterReachability(key, cluster);
964
+ this.clusterReachability[key] = new ClusterReachability(
965
+ key,
966
+ cluster,
967
+ // @ts-ignore
968
+ this.webex.config.meetings.enablePerUdpUrlReachability
969
+ );
965
970
  this.clusterReachability[key].on(Events.resultReady, async (data: ResultEventData) => {
966
971
  const {protocol, result, clientMediaIPs, latencyInMilliseconds} = data;
967
972
 
@@ -243,7 +243,9 @@ export class ReachabilityPeerConnection extends EventsScope {
243
243
  if (result.latencyInMilliseconds === undefined) {
244
244
  LoggerProxy.logger.log(
245
245
  // @ts-ignore
246
- `Reachability:ReachabilityPeerConnection#saveResult --> Successfully reached ${this.clusterName} over ${protocol}: ${latency}ms`
246
+ `Reachability:ReachabilityPeerConnection#saveResult --> Successfully reached ${
247
+ this.clusterName
248
+ } over ${protocol}: ${latency}ms, serverIp=${serverIp || 'unknown'}`
247
249
  );
248
250
  result.latencyInMilliseconds = latency;
249
251
  result.result = 'reachable';
@@ -1,4 +1,5 @@
1
- import {deleteNestedObjectsWithHtMeta} from '../../../../src/hashTree/utils';
1
+ import {HashTreeObject, ObjectType} from '../../../../src/hashTree/types';
2
+ import {deleteNestedObjectsWithHtMeta, isSelf} from '../../../../src/hashTree/utils';
2
3
 
3
4
  import {assert} from '@webex/test-helper-chai';
4
5
 
@@ -100,4 +101,40 @@ describe('Hash Tree Utils', () => {
100
101
  });
101
102
  });
102
103
  });
104
+
105
+ describe('#isSelf', () => {
106
+ ['self', 'SELF', 'Self'].forEach((type) => {
107
+ it(`should return true for object with type="${type}"`, () => {
108
+ const selfObject = {
109
+ htMeta: {
110
+ elementId: {
111
+ type,
112
+ id: 1,
113
+ version: 1,
114
+ },
115
+ dataSetNames: [],
116
+ },
117
+ data: {},
118
+ };
119
+
120
+ assert.isTrue(isSelf(selfObject as HashTreeObject));
121
+ });
122
+ });
123
+
124
+ it('should return false for non-self object', () => {
125
+ const participantObject = {
126
+ htMeta: {
127
+ elementId: {
128
+ type: ObjectType.participant,
129
+ id: 2,
130
+ version: 1,
131
+ },
132
+ dataSetNames: [],
133
+ },
134
+ data: {},
135
+ };
136
+
137
+ assert.isFalse(isSelf(participantObject));
138
+ });
139
+ });
103
140
  });